Compare commits

...

219 Commits

Author SHA1 Message Date
5cdc08f6ea Vertex spirv 2023-01-05 17:00:43 +02:00
8779cb7785 renderer_opengl: Fix shader compilation
* Also use glCopyImageSubData to do texture copies'
2023-01-02 16:22:51 +02:00
a6ca7dca61 vk_instance: Don't specify uint8 extension twice
* I have no idea why this happened
2023-01-02 15:40:06 +02:00
cce6a79a91 vk_shader_gen_spv: Fix OpCompositeConstruct bug
* Fixes graphics on the 2d_shapes homebrew and maybe other games
2023-01-02 15:40:00 +02:00
230a463a39 msvc ci: Setup Vulkan SDK for glslangValidator 2023-01-02 00:25:28 +02:00
83e734bd6a vk_shader_gen: new is a reserved keyword on Metal 2022-12-31 20:24:31 +02:00
72ee29669a renderer_vulkan: Add support for VK_KHR_image_format_list
* May help drivers when using mutable images
2022-12-31 18:20:08 +02:00
b1a02e1710 renderer_vulkan: Remove upload_cmdbuf
* No longer needed with the new stream buffer
2022-12-31 17:44:15 +02:00
2c34f41747 vk_rasterizer: Don't bind redundant bindings 2022-12-31 10:07:35 +02:00
60d59730a9 Revert "ci: Fix macOS upload script calling wrong macpack."
This reverts commit 3b9ed5234d.
2022-12-30 18:08:20 +02:00
850ec1f8b8 vk_common: Enable beta extensions
* Required to access the portability subset'
2022-12-30 16:22:53 +02:00
3da6c25fd8 renderer_vulkan: Fallback to software shaders on unsupported topology
* MoltenVK does not support triangle fans
2022-12-30 15:25:27 +02:00
0e987959a6 renderer_vulkan: Rewrite data streaming
* Most GPUs nowadays provide a device local/host visible memory heap which is useful for avoiding copies between staging and local memory and especially beneficial for mobile and APUs that are mostly the target of this backend.

* This commit ports the old yuzu stream buffer with some changes to suit our needs and gets rid of the buffer flush methods
2022-12-30 11:10:49 +02:00
410b8b8809 vk_texture_runtime: Tune barriers
* Using eAllCommandBit is really bad for Mali GPUs. Also most access flags were redundant mostly for edge cases.

* To address this track surface usage and decide the best barrier flags at runtime. This gives a significant performance boost to mobile GPUs
2022-12-29 21:56:57 +02:00
d3392ae0b1 renderer_vulkan: Properly format structs 2022-12-29 20:11:57 +02:00
98e0ecf6a7 renderer_vulkan: Add fallback path for VK_EXT_index_type_uint8
* Also remove some flush barriers
2022-12-29 19:07:09 +02:00
ad45b9880d android: Add vulkan support to frontend 2022-12-28 14:01:50 +02:00
0d1646e4df Revert "vk_scheduler: Enable usage of jthread on macos"
This reverts commit 09dcd48257.
2022-12-26 17:15:21 +02:00
96f0746ab9 HACK: Skip normquat lerp to drop geometry shader requirement 2022-12-26 15:50:11 +02:00
09dcd48257 vk_scheduler: Enable usage of jthread on macos
- Import the yuzu polyfill libraries
2022-12-26 11:17:23 +02:00
62fc1f835e ci: Provide glslang 2022-12-26 09:01:05 +02:00
3b351c33d1 android: Fix build 2022-12-26 08:53:30 +02:00
793485d201 renderer_vulkan: Revert some stream buffer changes
* The previous design was much less prone to errors so switch back to that. Also make 16 byte alignment standard
2022-12-25 23:48:11 +02:00
3ef5ab7323 video_core: Move pixel format functions to cpp file 2022-12-25 23:37:28 +02:00
618c80c803 vk_instance: Address small issues 2022-12-25 23:21:44 +02:00
f7cb308243 video_core/host_shaders: Add CMake integration for string shaders 2022-12-25 23:17:39 +02:00
b8583f9af3 renderer_vulkan: Port per-game to vulkan renderer 2022-12-25 23:06:30 +02:00
33bf2b7c2d renderer_vulkan: Forward validation errors to logfile 2022-12-25 22:31:27 +02:00
d48e6c04ce video_core: Move most pica register handling to RasterizerAccelerated 2022-12-25 22:31:27 +02:00
88f34a7d69 vk_shader_gen_spv: Implement proctex sampler
* Fixes MHS menu and probably other games
2022-12-25 22:31:27 +02:00
c8e9b465e2 renderer_vulkan: Proper telemetry reporting 2022-12-25 22:31:27 +02:00
278198f5f5 vk_shader_gen_spv: Implement shadow plane sampling 2022-12-25 22:31:27 +02:00
58718e6bd6 renderer_vulkan: Fix LMDM crashes
* Cache vertex format lookups to avoid crashing the driver when the game does too many lookups

* Increase watcher count. For some reason the game uses way too many watchers

* Fix other misc SPIRV bugs and don't crash when shadow is requested
2022-12-25 22:31:27 +02:00
a814b21693 vk_shader_gen_spv: Implement alpha testing
* Should fix all current graphical bugs
2022-12-25 22:31:27 +02:00
5478a4d634 vk_instance: Make DynamicLoader static
* That way we don't construct/destroy it all the time
2022-12-25 22:31:27 +02:00
8f87586495 citra_qt: Add SPIR-V shader option 2022-12-25 22:31:27 +02:00
7e3a0f524c externals: Update sirit 2022-12-25 22:29:05 +02:00
922019cc22 renderer_vulkan: Move fragment shader microprofile to a better place 2022-12-25 22:29:05 +02:00
41e9cdb645 shader_cache: Fix type deduction 2022-12-25 22:29:05 +02:00
e90add52d2 cmake: Fork sirit
* Upstream is missing some required instructions
2022-12-25 22:29:04 +02:00
3c6ca2cc82 renderer_vulkan: Begin new fragment shader SPIR-V emitter 2022-12-25 22:29:04 +02:00
d1039d9a81 code: Address build issues 2022-12-25 22:29:04 +02:00
8b8cee1a5a vk_instance: Remove depth clip control feature
* To not crash drivers that don't support it since we don't require it anymore
2022-12-25 22:28:49 +02:00
5dc92cd72b ci: Bundle MoltenVK with macOS builds. 2022-12-25 22:28:49 +02:00
d1503605a7 vulkan: Fix supported extension check 2022-12-25 22:28:49 +02:00
6426f7a319 vulkan: Use required portability instance extension on macOS. 2022-12-25 22:28:49 +02:00
3b9ed5234d ci: Fix macOS upload script calling wrong macpack. 2022-12-25 22:28:49 +02:00
763127605e qt: Extract CAMetalLayer from NSView to pass to MoltenVK. 2022-12-25 22:28:49 +02:00
b86b19d366 renderer_vulkan: Drop requirement for VK_EXT_depth_clip_control 2022-12-25 22:28:49 +02:00
3dd74c69c5 Revert "CI: dont upload macos artifacts (#6121)"
This reverts commit 30831e6367.
2022-12-25 22:28:49 +02:00
01af8e3f2c renderer_vulkan: Integrate MacOS wsi 2022-12-25 22:28:49 +02:00
474cccda33 video_core: Fix build issues on macos 2022-12-25 22:28:49 +02:00
6057b18172 renderer_vulkan: Emulate 3-component vertex formats when unsupported
* This fixes the crashes on AMD
2022-12-25 22:28:48 +02:00
6a4ff8fa24 renderer_vulkan: Emulate logic op when unsupported
* Similar to GLES this is done to prepare for the android port
2022-12-25 22:28:48 +02:00
3c79360fd3 gl_rasterizer: Cleanup and fix bugs 2022-12-25 22:28:48 +02:00
939aafed40 vk_texture_runtime: Implement RGBA4 converter
* Fixes graphics in NES Remix. Need to also do a reinterpreter some time, but this will suffice for now
2022-12-25 22:28:48 +02:00
23417787f8 texture_downloader_es: Remove invalid operations 2022-12-25 22:28:48 +02:00
8946c1a7de gl_texture_runtime: Use OGLStreamBuffer for uploads/downloads
* Much better than the current implementation
2022-12-25 22:28:48 +02:00
5fe910b18f vk_stream_buffer: Cleanup flush barrier 2022-12-25 22:28:48 +02:00
3944cbdc19 video_core: Reorder microprofile defines 2022-12-25 22:28:48 +02:00
8ac3dd1840 renderer_vulkan: Rewrite stream buffer, again...
* The previous implemention was fine, but it wasted space. Buckets now are just ticks attached to a particular buffer region, which means we can flush/map arbitrary regions

* A bug in the texture runtime is also fixed which commited to the same buffer twice
2022-12-25 22:28:15 +02:00
ff34287e4b renderer_vulkan: Pack PicaFSConfig
* Using bitfields the struct size was reduced from 420 to 190 bytes. which should speed up hashing and copying to the worker thread
2022-12-25 22:28:15 +02:00
81f2a0eaa1 renderer_vulkan: Cleanup vertex array setup
* Also the function would commit more data then it requested leading to out of bound crashes
2022-12-25 22:28:15 +02:00
f11715a4f4 renderer_vulkan: Remove AttribType
* Use VertexAttributeFormat to avoid unnecessary enum casts
2022-12-25 22:28:15 +02:00
89c51371f7 video_core: Move HardwareVertex to RasterizerAccelerated 2022-12-25 22:28:15 +02:00
8076d893db video_core: Move api agnostic uniform updates to RasterizerAccelerated 2022-12-25 22:28:15 +02:00
72f8d520c9 renderer_vulkan: Fix swapchain resizing 2022-12-25 22:28:15 +02:00
f9274f8b9a renderer_vulkan: Add single-thread record ability to the scheduler
* Async is pretty nice but games that do a lot of flushes might have worse performance due to thread synchronization overhead

* I haven't noticed any cases of this yet but it doesn't hurt making this a UI option
2022-12-25 22:28:15 +02:00
3c09c03180 citra_qt: Refuse to enable debug option if the layers are not available 2022-12-25 22:24:28 +02:00
52251e3908 renderer_vulkan: Scheduler and presentation rewrite
* This commit ports yuzu's async scheduler replacing our older and crummier version
  Commands are recorded by the scheduler and processed by a separate worker thread.

* Queue submission is also moved to the worker thread which should alliviate slowdowns related to vkQueueSubmit stalls

* Fragment shader compilation and queue submission are also moved to that thread to reduce stutters
2022-12-25 22:23:46 +02:00
921444c2c9 externals: Update vulkan-headers 2022-12-25 22:23:46 +02:00
89d234f642 common: Remove concepts usage 2022-12-25 22:23:46 +02:00
13771b805b renderer_vulkan: Fix shader hash type 2022-12-25 22:23:46 +02:00
2a71059490 code: Remove usages of std::ranges
* MacOS is still runining my C++ 20 fun
2022-12-25 22:23:46 +02:00
053221f155 renderer_vulkan: Prefer immediate over mailbox present mode 2022-12-25 22:23:46 +02:00
4868c361e7 renderer_vulkan: Bump vertex buffer size
* So software shaders don't crash
2022-12-25 22:23:46 +02:00
0c30dbf33e renderer_vulkan: Add more microprofile targets 2022-12-25 22:23:46 +02:00
53370e81e2 renderer_vulkan: Improve StreamBuffer API and use it in TextureRuntime
* Also use separate upload and download buffers optimized for write and readback respectively. This gives a huge 20+ FPS boost in most games which were bottlenecked by slow reads
2022-12-25 22:23:46 +02:00
b3fb260c84 renderer_vulkan: Fix allocation caching bug 2022-12-25 22:23:46 +02:00
599ca7caf7 renderer_opengl: Port scaled upload/download code from vulkan 2022-12-25 22:23:46 +02:00
abc0fd5e7b renderer_vulkan: Include algorithm in vk_common
* Appears to be a bug in vulkan-hpp
2022-12-25 22:23:46 +02:00
309b25d201 renderer_vulkan: Use linear filtering when possible
* Fixes blocky artifacts in Samus Returns
2022-12-25 22:23:46 +02:00
9ac7ef20b0 renderer_vulkan: Abstract descriptor management
* The pipeline cache was starting to get cluttered
2022-12-25 22:23:46 +02:00
ebade3594d renderer_vulkan: Bump descriptor set allocation limit 2022-12-25 22:23:46 +02:00
dca159d79f renderer_vulkan: Fix storage descriptor binding and respect color mask
* RGBA8 surfaces now expose an additional R32Uint view used for storage descriptors. The format is guaranteed by the spec to support atomic loads/stores. This requires the mutable flag which incurs a performance cost, but might be better than breaking the current renderpass each draw when rendering shadows, especially on mobile

* Color mask is also implemented which fixes Street Fighter and Monster Hunter Stories
2022-12-25 22:23:46 +02:00
7007d5822a renderer_vulkan: Implement depth uploads with blit 2022-12-25 22:23:46 +02:00
6f0fdf037f renderer_vulkan: Use intermediate copy when framebuffer is used both as attachment and shader input 2022-12-25 22:23:46 +02:00
58621b0eb6 renderer_vulkan: Respect disk shader option 2022-12-25 22:23:46 +02:00
e8eef5c586 renderer_vulkan: Fix staging buffer size 2022-12-25 22:23:46 +02:00
2b37997a95 renderer_vulkan: Catch and log more runtime errors
* Also add the ability to enable command buffer dumping which is very useful
2022-12-25 22:23:15 +02:00
558062efd7 renderer_vulkan: Batch allocate descriptor sets
* Less driver calls should lead to better performance
2022-12-25 22:20:22 +02:00
5e880a4f26 renderer_vulkan: Emulate border color if possible 2022-12-25 22:20:22 +02:00
238956f773 renderer_vulkan: Implement scaled uploads and downloads
* This commit includes large changes to have textures are handling. Instead of using ImageAlloc, Surface is used instead which provides multiple benefits: automatic recycling on destruction and ability to use the TextureRuntime interface to simplify operations

* Layout tracking is also implemented which allows transitioning of individual mip levels without errors

* This fixes graphical errors in multiple games which relied on framebuffer uploads
2022-12-25 22:20:22 +02:00
a5351bc596 renderer_vulkan: Fix renderpass issues
* The cache didn't take into account the framebuffer and render area used, so if these changed the renderpass wouldn't restart. This caused graphical bugs in Pokemon X/Y
2022-12-25 22:20:22 +02:00
5b6e99b194 renderer_vulkan: Update stencil compare mask 2022-12-25 22:20:22 +02:00
3f3b4a2802 citra_qt: Fix graphics api indicator alignment 2022-12-25 22:20:22 +02:00
c19e8d36c9 renderer_opengl: Fix OpenGLES issues
* Always request a 4.4 context until I figure out how to get Qt to cooperate

* Use RGBA for BGR since the converted table will do that conversion
2022-12-25 22:20:22 +02:00
2ec31f404b renderer_vulkan: Report perf stats 2022-12-25 22:20:21 +02:00
28a2805450 renderer_vulkan: Better error handling 2022-12-25 22:20:21 +02:00
2601a1df6c renderer_vulkan: Allow direct allocation of images 2022-12-25 22:19:26 +02:00
96d5bb553b renderer_vulkan: Fix incorrect depth format detection
* Intel iGPUs don't support blit on all depth/stencil formats which caused issues since the runtime checks for this while the renderpass cache does not
2022-12-25 22:19:26 +02:00
b4d0f442c8 renderer_vulkan: Actually minize state changes
* Keep track of the current state and only update it when needed. Previously games would set the same state over and over cluttering renderdoc logs
2022-12-25 22:19:25 +02:00
2a68cab7d6 renderer_vulkan: Fix broken sync without timeline semaphores 2022-12-25 22:19:25 +02:00
009d73fdf6 renderer_vulkan: Allocate descriptor sets during reinterpretation 2022-12-25 22:19:25 +02:00
42af22f8fd renderer_vulkan: Enable logic ops and fix swapchain resizing 2022-12-25 22:19:25 +02:00
b85d15b035 renderer_vulkan: Clear stencil with renderpass
* Fixes outline retension in pokemon games
2022-12-25 22:19:25 +02:00
130e376c0c renderer_vulkan: Fix pipeline cache crashes 2022-12-25 22:19:25 +02:00
f884986257 renderer_vulkan: Optimize tiled format convertion + fix vertex buffer alignment
* Integrate format convertion to the morton copy function, removing the need for an intermediate copy and convertion pass. This should be beneficial for performance especially since most games use tiled textures

* Also bump vertex buffer size to avoid crashes with hardware shaders and provide correct offset on normal draws which fixes glitches in pokemon Y

* Reduce the local group size to 8 in the D24S8 compute shader which fixes graphical issues in the afformentioned pokemon games at native resolution

* Set LOD to 0 instead of 0.25 to fix another glitch in pokemon y
2022-12-25 22:19:25 +02:00
e23dc3efb1 renderer_opengl: Fix broken texture copy
* Resolves graphical bugs in Professor Layton vs Ace Attorney when using OpenGL
2022-12-25 22:19:25 +02:00
dd5e95d7c6 renderer_vulkan: Pipeline cache fixes
* Delete cache file if found invalid

* Name it after the vendor/device ids so each physical devices gets a separate cache
2022-12-25 22:19:25 +02:00
b72289eadd video_core: Fix renderpass cache bug and introduce RGBA -> BGR converter 2022-12-25 22:19:25 +02:00
7a5d4f03da renderer_opengl: Specify precision in compute shader and add RGB5A1 converter
* Fixes OpenGLES crash
2022-12-25 22:19:25 +02:00
18fa277c71 renderer_vulkan: Complete hardware shader support
* With these changes all commercial games I tested work fine and get a massive performance boost
2022-12-25 22:19:25 +02:00
73cc764091 renderer_vulkan: Begin hardware shader support
* Still experimental and works only with homebrew
2022-12-25 22:19:25 +02:00
9dea514d45 citra: Fix build issues with MinGW and MSVC 2022-12-25 22:19:25 +02:00
0bfaa035b9 renderer_vulkan: Fix warnings and cleanup 2022-12-25 22:19:06 +02:00
85df778785 code: Run clang-format 2022-12-25 22:19:06 +02:00
e54a92c252 code: Address build issues 2022-12-25 22:18:04 +02:00
9145d4cec8 video_core: Re-implement format reinterpretation
* Same as before but D24S8 to RGBA8 is switched to a compute shader which should provide better throughput and is much simpler to implement in Vulkan
2022-12-25 22:18:04 +02:00
c611592db6 citra_qt: Add physical device selection dialog 2022-12-25 22:18:04 +02:00
6aba809da8 renderer_opengl: Unbind unused framebuffer targets
* Fixes graphical glitches in many games for some reason
2022-12-25 22:12:42 +02:00
f0449d79fd renderer_opengl: Emulate texture copy with blit for now 2022-12-25 22:12:42 +02:00
33481ada7f renderer_opengl: Address buffer overflow 2022-12-25 22:12:42 +02:00
67195974e7 video_core: Small code improvements 2022-12-25 22:12:42 +02:00
70c2376fd0 renderer_vulkan: Don't sample from mipmaps when using texture cubes
* Mipmaps for texture cubes are unimplemented in the rasterizer cache, so sampling from mipmaps will return nothing
2022-12-25 22:12:42 +02:00
177c7de4f9 input_common: Small fix 2022-12-25 22:12:42 +02:00
eea914ba84 citra_qt: Improve graphics API intergration
* Add renderer debug option which toggles debug output in OpenGL/validation layers in Vulkan

* Fix many warnings and replace deprecated Qt functionailty with newer alternatives
2022-12-25 22:12:41 +02:00
62e88fbeb3 rasterizer_cache: Code cleanup
* Merge utils and types to a single header
2022-12-25 22:07:46 +02:00
5ce27d8341 texture_decode: Prefer std::memcpy where possible 2022-12-25 22:07:46 +02:00
eaf62eb635 renderer_vulkan: Rework format handling
* This is a pretty large commit that aims to solve some issues with the current format system
* The instance now builds at application initialization an array of format traits for each pixel format
  that includes information such as blit/attachment/storage support and fallback formats
* The runtime doesn't ask the instance for formats but receives these traits and can dedice on its own what to build
  For now we do the same as before, we require both blit and attachment support

* Morton swizzling also sees many bug fixes. The previous code was very hacky and didn't work for partial
  texture updates. It was also inconsistent, as it would take a tiled_buffer and write to the middle of linear
* Now the functions have been greatly simplified and adjusted to work better with std::span. This fixes out of bounds
  errors and texture glitches (like the display in Mario Kart 7)
2022-12-25 22:07:46 +02:00
9675811bbe renderer_vulkan: Add experimental Vulkan renderer 2022-12-25 22:07:46 +02:00
945faf8e92 externals: Add vulkan headers and vma 2022-12-25 22:02:59 +02:00
9403049671 rasterizer_cache: Refactor texture cube interface
* Reuse our Surface class instead of having a separate one, to avoid reimplementing stuff in the backend
2022-12-25 22:02:59 +02:00
40159d9779 gl_texture_runtime: Clean up texture upload/download code
* Improve readability and code clarity
2022-12-25 22:02:59 +02:00
f63653a5b9 rasterizer_cache: Use Common::Rectangle everywhere
* Make a nice alias for it and use it instead of having Rect2D/Region2D. Makes the new design less intrusive to the current cache
2022-12-25 22:02:59 +02:00
c71dbb5d19 rasterizer_cache: Make into template
* This is the final step, now RasterizerCache is compltely decoupled from OpenGL (technically not yet, but that's talking details). For now texture filtering and some GLES paths have been disabled and will be reimplemented in the following commits
2022-12-25 22:02:59 +02:00
0f4df2c012 rasterizer_cache: Use PBO staging buffer cache for texture uploads/downloads 2022-12-25 22:02:11 +02:00
c6fc4f5a87 rasterizer_cache: Reorder methods 2022-12-25 22:02:11 +02:00
916afa194d rasterizer_cache: Remove remnants of cached_pages 2022-12-25 22:02:11 +02:00
6f2cd11a85 rasterizer_cache: Fix texture cube blitting
* The target was GL_TEXTURE_2D instead of GL_TEXTURE_CUBE_MAP_*
2022-12-25 22:02:11 +02:00
14652d52bc morton_swizzle: Implement texture formats in UNSWIZZLE_TABLE
* I can now remove that loop that has been messing with my OCD
2022-12-25 22:02:11 +02:00
a57ee7cdf2 morton_swizzle: Use tiled_buffer instead of reading data from g_memory
* It's much safer and removes hardcoded global state usage
2022-12-25 22:02:11 +02:00
dbd3e6c29b rasterizer_accelerated: Zero intialize cached_pages
* Resolves random crashes because count takes random values
2022-12-25 22:02:11 +02:00
665cbca17c texture_runtime: Add staging buffer lock mechanism 2022-12-25 22:02:11 +02:00
efb9e9f40f cached_surface: Remove custom texture logic
* Makes things more complicated and is in the way. It's probably already
broken by recent changes, so I'll need to reimplement it anyway
2022-12-25 22:02:11 +02:00
8d35118f63 renderer_opengl: Add driver class to report info/bugs 2022-12-25 22:02:11 +02:00
553c85456e rasterizer_cache: Add staging buffer cache for uploads/downloads
*  In addition bump context version to 4.4 to enforce ARB_buffer_storage and use EXT_buffer_storage for GLES which is support on many mobile devices
2022-12-25 22:02:11 +02:00
68ca206d53 rasterizer_cache: Improve TextureRuntime API
* This makes every operation more explicit and mimics more the Vulkan API
2022-12-25 22:02:11 +02:00
e30e977140 renderer_opengl: Encapsulate sync objects in OGLSync 2022-12-25 22:02:10 +02:00
f13738d252 morton_swizzle: Optimize and use std::span 2022-12-25 22:02:10 +02:00
04b927ab7f morton_swizzle: Avoid buffer underflow
* Check the y coordinate before decrementing linear_buffer
2022-12-25 22:02:10 +02:00
993d172de9 morton_swizzle: Move out of bounds texture check out of the decode loop
* Running relative expensive checks like this on a hot path causes small but measurable performance loss. Tested SMD wit this and it doesn't crash
2022-12-25 22:02:10 +02:00
695447611e rasterizer_cache: Use SurfaceType instead of Aspect
* It was doing pointless enum conversions when both enums described the same thing
2022-12-25 22:02:10 +02:00
06bacfbd72 rasterizer_cache: Separate texture swizzling to utils 2022-12-25 22:02:10 +02:00
cf8bc35d46 rasterizer_cache: Remove OpenGL references from morton_swizzle 2022-12-25 22:02:10 +02:00
ef859bab84 citra_qt: Forbid renderer change during runtime
* It's an endless source of problems and isn't usefull
2022-12-25 22:02:10 +02:00
a2d0669562 rasterizer_cache: Touch up MatchFlags comments 2022-12-25 22:02:10 +02:00
95365ad6ba rasterizer_cache: Drop OpenGL postfix 2022-12-25 22:02:10 +02:00
1963b649e8 rasterizer_cache: Shorten filenames and general cleanup
* AllocateSurfaceTexture now takes the PixelFormat directly as FormatTuple is an OpenGL struct and will be moved there
2022-12-25 22:02:10 +02:00
db7cdb741c video_core: Move UpdatePagesCachedCount to RasterizerAccelerated 2022-12-25 22:02:10 +02:00
0fe61ba040 citra_qt: Prepare GUI for Vulkan support 2022-12-25 22:02:06 +02:00
b588d6181b qt_multimedia_camera: fix image handling in Qt 5.15+ ... (#6231)
... In Qt 5.15+ the QImage will not accept non-natively handled pixel
formats anymore. We can however use the newly added conversion method
provided in `QVideoFrame` to convert it to `QImage` instead
2022-12-24 11:45:31 +05:30
c8ff1d744a Change Monoscopic Render mode to a dropdown (#6215) 2022-12-24 03:17:49 +01:00
ae3d50f71f Port yuzu-emu/yuzu#2968: configure_input: Fix input handling for ZL and ZR from controllers with analog triggers (#4984)
Co-authored-by: Frederic L. <freddyfunk@users.noreply.github.com>
2022-12-17 16:11:49 +01:00
ccb50e7f2c Port yuzu-emu/yuzu#9300: "CMake: Use precompiled headers to improve compile times" (#6213)
Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>
2022-12-17 16:06:38 +01:00
51e252c7ed Hide mouse: fix for secondary window and single window mode (#6220) 2022-12-17 16:05:51 +01:00
517e0bc342 Camera fixes (#6181) 2022-12-17 16:05:04 +01:00
812c4fa059 Per-Game Settings: options to reset game settings (#6219) 2022-12-17 16:04:31 +01:00
cb82ffbe20 code: Small cleanups and fixes to lambda captures (#6191)
Co-authored-by: v1993 <v19930312@gmail.com>
Co-authored-by: Morph <39850852+Morph1984@users.noreply.github.com>
2022-12-17 16:04:10 +01:00
9d5ae8e1c2 service/nwm_uds: Add NetworkStatusChangeReason (#5377)
fixes https://github.com/citra-emu/citra/issues/3975
2022-12-17 16:03:59 +01:00
06a9f69d88 hle/service/cfg: Add Config block enums documented by 3dbrew... (#6206) 2022-12-14 01:48:41 +01:00
84eada8c50 Fix "auto" resolution factor (#6218) 2022-12-13 01:05:08 +01:00
016ce6c286 Add 3GX plugin loader (#6172)
* Initial plugin loader support

* More plugin loader progress

* Organize code and more plugin features

* Fix clang-format

* Fix compilation and add android gui

* Fix clang-format

* Fix macos build

* Fix copy-paste bug and clang-format

* More merge fixes

* Make suggestions

* Move global variable to static member

* Fix typo

* Apply suggestions

* Proper initialization order

* Allocate plugin memory from SYSTEM instead of APPLICATION

* Do not mark free pages as RWX

* Fix plugins in old 3DS mode.

* Implement KernelSetState and notif 0x203

* Apply changes

* Remove unused variable

* Fix dynarmic commit

* Sublicense files with MIT License

* Remove non-ascii characters from license
2022-12-11 10:08:58 +02:00
48ee112ceb Add per game configuration options (#6187)
* common: Move settings to common from core.

- Removes a dependency on core and input_common from common.

* code: Wrap settings values

* Port from yuzu to allow per game settings

* citra_qt: Initial per-game settings dialog

* citra_qt: Use new API for read/save of config values

* citra_qt: Per game audio settings

* citra_qt: Per game graphics settings

* citra_qt: Per game system settings

* citra_qt: Per game general settings

* citra_qt: Document and run clang format

* citra_qt: Make icon smaller and centered

* citra_qt: Remove version number

* Not sure how to extract that, can always add it back later

* citra_qt: Wrap UISettings

* citra_qt: Fix unthottled fps setting

* citra_qt: Remove margin in emulation tab

* citra_qt: Implement some suggestions

* Bring back speed switch hotkey

* Allow configuration when game is running

* Rename/adjust UI stuff

* citra_qt: Fix build with separate windows

* citra_qt: Address feedback

* citra_qt: Log per-game settings before launching games

* citra_qt: Add shader cache options

* Also fix android build

* citra_qt: Add DLC menu option

* citra_qt: Run clang-format

* citra_qt: Adjust for time offset

* citra_qt: Implement suggestions

* Run clang-format

Co-authored-by: bunnei <bunneidev@gmail.com>
2022-12-08 13:27:25 +02:00
f261daf2fa Attempt to hotfix Android CI (assume /usr/bin/apksigner is no longer a jar) (#6210) 2022-12-08 07:25:11 +05:30
0fc177e268 configure_motion_touch: do not move engine parameter (#6208)
fixes an error stemmed from miscommunication
2022-12-06 21:52:25 -03:00
d1171328c9 Qt: Implement more hotkeys for secondary window (#6198) 2022-11-24 19:25:27 +05:30
850e5bf81c Merge pull request #6194 from merryhime/more-accurate-cycle-counting
arm/dynarmic: More accurate cycle counting
2022-11-24 19:25:01 +05:30
6da59d581e arm_tick_counts: Thumb implementation 2022-11-22 22:52:37 +00:00
9c0fad21db arm_tick_counts: Implement ARM cycle counting 2022-11-22 22:52:37 +00:00
0f250bed89 arm_tick_counts: Skeleton implementation 2022-11-22 22:52:37 +00:00
7cae35024f common: Add StringLiteral 2022-11-22 22:52:37 +00:00
f298278f37 arm/dynarmic: Initial implementation for instruction tick counts 2022-11-22 22:52:37 +00:00
2238e6c3ef externals: Update dynarmic to 7a926d689bcc1cc39dd26d5bba379dffcc6815a3 2022-11-22 22:52:37 +00:00
d09f6d97f6 Merge pull request #6196 from vitor-k/stoll-exception
Resolve C4101 warning
2022-11-21 19:50:15 -03:00
1e6507d792 Resolve C4101 warning
and catch logic_error instead of exception, as stoll should only throw std::invalid_argument and std::out_of_range, and both inherit from it.
2022-11-20 22:41:33 -03:00
64062162c6 feat: add system time offset setting (#6139)
* Add setting for system time offset

Add a setting to displace citra system time by days, hours, minutes
or seconds
Add UI for the setting which is only visible when clock is set to
system time
Change core/settings.h to include the setting

* Add system time offset to kernel

Actually makes use of the time offset.

* Fix time offset calculatioon in core/movie.cpp

* Replace C++20 chrono::days with seconds

Hopefully fixes the build.
2022-11-20 17:34:53 +02:00
3b6ffd9c27 Add MSVC binaries for Windows on releases (#6190)
* Improve directory creation in WindowsCopyFiles.cmake

* Release msvc compiled binaries as an alternative to mingw releases

* msvc: do not ship .pdb files

* msvc: Copy necessary files to the release

* windows-msvc: enable compatibility reporting

translation was disabled because the bundled qt doesn't have
the necessary component

Co-authored-by: Michał Janiszewski <janisozaur@users.noreply.github.com>
2022-11-19 22:22:10 +02:00
f44c95d638 Add "Separate Windows" LayoutOption (#6177) 2022-11-17 16:37:30 +01:00
4f715b6718 Add nearest neighbor texture filter (#6189)
Closes https://github.com/citra-emu/citra/issues/4707
closes https://github.com/citra-emu/citra/issues/5274
2022-11-16 18:55:14 +01:00
664562f988 add mono_render_left_eye option (#6140) 2022-11-16 18:54:26 +01:00
2967068b87 Merge pull request #5380 from FearlessTobi/port-3954
Port yuzu-emu/yuzu#3954: "main: Log host system memory parameters"
2022-11-15 11:31:59 +00:00
d89a6d491e Merge pull request #5526 from FearlessTobi/port-4535-4548
Port yuzu-emu/yuzu#4535 and yuzu-emu/yuzu#4548: Changes to the Common namespace
2022-11-15 11:30:21 +00:00
95c7bac8a6 Resolve compilation errors related to the Color namespace change 2022-11-15 11:25:51 +01:00
81bf21283f common/color: Migrate code over to the Common namespace
No external code makes use of this header, so we can freely change the
namespace.
2022-11-15 11:25:51 +01:00
de1fe7e6e3 Address review comments 2022-11-15 11:20:35 +01:00
35f7f5e3e3 common: switch to nullptr for sysctl's empty new value 2022-11-15 10:34:56 +01:00
6dabf10009 common: add sysconf() fallback
src/common/memory_detect.cpp:15:10: fatal error: 'sys/sysinfo.h' file not found
 #include <sys/sysinfo.h>
          ^~~~~~~~~~~~~~~
2022-11-15 10:34:56 +01:00
cb8b72069f Fix macOS code and change "Swapfile" to "Swap" 2022-11-15 10:34:56 +01:00
a67f205cfe main: Log host system memory parameters
Logs both physical memory and swapfile sizes, this is useful for support.
2022-11-15 10:34:56 +01:00
94d0399876 fix clang format on mingw/msys2 (#6188) 2022-11-13 09:22:05 +02:00
f6320c8de9 Reduce bugginess when texture filtering and custom textures both enabled (#6184)
* fixed some (but not all) glitchy behavior when texture filtering & custom textures both enabled

* fix clang format
2022-11-12 08:08:26 +02:00
a76ef97f15 citra_qt: ask for confirmation when changing games from the game list (#6186)
Co-authored-by: nobody <nobody>
2022-11-12 10:39:53 +05:30
bb05d8c12a src/CMakeLists: Enforce multiple warnings on MSVC (#5692) 2022-11-09 23:14:28 +01:00
38b8bf12de Merge pull request #6168 from PabloMK7/cia_update_fix
Fix handling of auto-updating ncch apps
2022-11-09 17:35:16 +02:00
d04d71e4c9 Remove misplaced return statement 2022-11-09 16:25:52 +01:00
b9d9ae35e4 Merge pull request #6183 from GPUCode/master
citra_qt: Avoid null reference capture when taking screenshots
2022-11-09 16:50:31 +02:00
bd84dbc8d3 citra_qt: Avoid null reference capture when taking screenshots 2022-11-09 16:50:05 +02:00
c7e259366d CMake: Use qt-5.15.7 bundled externals for VS 2019/2022 (#6176) 2022-11-07 10:09:23 +05:30
92ad651890 Port yuzu-emu/yuzu#4290 and yuzu-emu/yuzu#4318: Changes for C++20 support (#5459)
Co-authored-by: Lioncash <mathew1800@gmail.com>
2022-11-06 03:35:20 +01:00
3201943423 Port yuzu-emu/yuzu#4437: "core_timing: Make use of uintptr_t to represent user_data" (#5499)
Co-authored-by: LC <lioncash@users.noreply.github.com>
2022-11-06 02:24:45 +01:00
7801907288 externals: Update dynarmic (#6175) 2022-11-05 11:19:50 +05:30
1ddea27ac8 code: Cleanup and warning fixes from the Vulkan PR (#6163)
Co-authored-by: emufan4568 <geoster3d@gmail.com>
Co-authored-by: Kyle Kienapfel <Docteh@users.noreply.github.com>
2022-11-04 23:32:57 +01:00
aa84022704 Port yuzu-emu/yuzu#4164: "hotkeys: Add a "Mute Audio" hotkey" (#5463)
Co-authored-by: Kewlan <colin_rehn@hotmail.com>
2022-11-04 20:25:57 +01:00
14924e9db3 Backport review comments from yuzu-emu/yuzu#4382: "yuzu: Add motion and touch configuration from Citra" (#5543) 2022-11-04 20:25:50 +01:00
e14b9f7a25 Fix clang-format and unused include 2022-10-27 01:09:18 +02:00
fd7ada2a9c Fix chainloading for all apps 2022-10-23 22:55:50 +02:00
d396944487 Fix self updating ncch app jump 2022-10-21 01:12:29 +02:00
c49379442d Fix auto updating ncch files 2022-10-20 23:14:57 +02:00
391 changed files with 49506 additions and 10313 deletions

View File

@ -17,7 +17,7 @@ then
echo "Signing apk..."
base64 --decode <<< "${ANDROID_KEYSTORE_B64}" > ks.jks
java -jar $(which apksigner) sign --ks ks.jks \
apksigner sign --ks ks.jks \
--ks-key-alias "${ANDROID_KEY_ALIAS}" \
--ks-pass env:ANDROID_KEYSTORE_PASS "artifacts/${REV_NAME}.apk"
fi

View File

@ -3,7 +3,7 @@
brew update
brew unlink python@2 || true
rm '/usr/local/bin/2to3' || true
brew install qt5 p7zip ccache ninja || true
brew install qt5 molten-vk glslang vulkan-loader p7zip ccache ninja || true
pip3 install macpack
export SDL_VER=2.0.16

View File

@ -12,20 +12,37 @@ cp build/bin/Release/citra "$REV_NAME"
cp -r build/bin/Release/citra-qt.app "$REV_NAME"
cp build/bin/Release/citra-room "$REV_NAME"
# move libs into folder for deployment
macpack "${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt" -d "../Frameworks"
# move qt frameworks into app bundle for deployment
$(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" -executable="${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt"
BUNDLE_PATH="$REV_NAME/citra-qt.app"
BUNDLE_CONTENTS_PATH="$BUNDLE_PATH/Contents"
BUNDLE_EXECUTABLE_PATH="$BUNDLE_CONTENTS_PATH/MacOS/citra-qt"
BUNDLE_LIB_PATH="$BUNDLE_CONTENTS_PATH/lib"
BUNDLE_RESOURCES_PATH="$BUNDLE_CONTENTS_PATH/Resources"
CITRA_STANDALONE_PATH="$REV_NAME/citra"
# move libs into folder for deployment
macpack "${REV_NAME}/citra" -d "libs"
macpack $BUNDLE_EXECUTABLE_PATH -d "../Frameworks"
# move qt frameworks into app bundle for deployment
$(brew --prefix)/opt/qt5/bin/macdeployqt $BUNDLE_PATH -executable=$BUNDLE_EXECUTABLE_PATH
# move libs into folder for deployment
macpack $CITRA_STANDALONE_PATH -d "libs"
# bundle MoltenVK
VULKAN_LOADER_PATH=$(brew --prefix vulkan-loader)
MOLTENVK_PATH=$(brew --prefix molten-vk)
mkdir $BUNDLE_LIB_PATH
cp $VULKAN_LOADER_PATH/lib/libvulkan.dylib $BUNDLE_LIB_PATH
cp $MOLTENVK_PATH/lib/libMoltenVK.dylib $BUNDLE_LIB_PATH
cp -r $MOLTENVK_PATH/share/vulkan $BUNDLE_RESOURCES_PATH
install_name_tool -add_rpath "@loader_path/../lib/" $BUNDLE_EXECUTABLE_PATH
# workaround for libc++
install_name_tool -change @loader_path/../Frameworks/libc++.1.0.dylib /usr/lib/libc++.1.dylib "${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt"
install_name_tool -change @loader_path/libs/libc++.1.0.dylib /usr/lib/libc++.1.dylib "${REV_NAME}/citra"
install_name_tool -change @loader_path/../Frameworks/libc++.1.0.dylib /usr/lib/libc++.1.dylib $BUNDLE_EXECUTABLE_PATH
install_name_tool -change @loader_path/libs/libc++.1.0.dylib /usr/lib/libc++.1.dylib $CITRA_STANDALONE_PATH
# Make the launching script executable
chmod +x ${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt
chmod +x $BUNDLE_EXECUTABLE_PATH
# Verify loader instructions
find "$REV_NAME" -type f -exec otool -L {} \;

View File

@ -1,7 +1,18 @@
#!/bin/sh -ex
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" -DCITRA_USE_CCACHE=ON -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON -DENABLE_FFMPEG_VIDEO_DUMPER=ON
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" \
-DCITRA_USE_CCACHE=ON \
-DCITRA_USE_BUNDLED_QT=1 \
-DENABLE_QT_TRANSLATION=OFF \
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DENABLE_MF=ON \
-DENABLE_FFMPEG_VIDEO_DUMPER=ON
ninja
# show the caching efficiency

View File

@ -0,0 +1,41 @@
$GITDATE = $(git show -s --date=short --format='%ad') -replace "-", ""
$GITREV = $(git show -s --format='%h')
# Find out what release we are building
if ( $GIT_TAG_NAME ) {
$RELEASE_NAME = ${GIT_TAG_NAME}.split("-")[0]
$RELEASE_NAME = "${RELEASE_NAME}-msvc"
}
else {
$RELEASE_NAME = "head"
}
$MSVC_BUILD_ZIP = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
$MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
$BUILD_DIR = ".\build\bin\Release"
# Create artifact directories
mkdir $RELEASE_NAME
mkdir "artifacts"
echo "Starting to pack ${RELEASE_NAME}"
Copy-Item $BUILD_DIR\* -Destination $RELEASE_NAME -Recurse
Remove-Item $RELEASE_NAME\tests.* -ErrorAction ignore
Remove-Item $RELEASE_NAME\*.pdb -ErrorAction ignore
# Copy documentation
Copy-Item license.txt -Destination $RELEASE_NAME
Copy-Item README.md -Destination $RELEASE_NAME
# Copy cross-platform scripting support
Copy-Item dist\scripting -Destination $RELEASE_NAME -Recurse
# Build the final release artifacts
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_NAME\*
7z a $MSVC_SEVENZIP $RELEASE_NAME
Copy-Item $MSVC_BUILD_ZIP -Destination "artifacts"
Copy-Item $MSVC_SEVENZIP -Destination "artifacts"

View File

@ -95,6 +95,13 @@ jobs:
env:
MACOSX_DEPLOYMENT_TARGET: "10.13"
ENABLE_COMPATIBILITY_REPORTING: "ON"
- name: Pack
run: ./.ci/macos/upload.sh
- name: Upload
uses: actions/upload-artifact@v3
with:
name: macos
path: artifacts/
windows:
runs-on: windows-latest
steps:
@ -108,16 +115,34 @@ jobs:
key: ${{ runner.os }}-win-${{ github.sha }}
restore-keys: |
${{ runner.os }}-win-
- name: Query tag name
uses: little-core-labs/get-git-tag@v3.0.2
id: tagName
- name: Install dependencies
run: ./.ci/windows-msvc/deps.sh
shell: bash
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
with:
vulkan-query-version: latest
vulkan-components: Glslang
vulkan-use-cache: true
- name: Test glslangValidator
run: glslangValidator --version
- name: Build
run: ./.ci/windows-msvc/build.sh
shell: bash
env:
ENABLE_COMPATIBILITY_REPORTING: "ON"
- name: Pack
run: ./.ci/windows-msvc/upload.ps1
- name: Upload
uses: actions/upload-artifact@v3
with:
name: msvc
path: artifacts/
android:
runs-on: ubuntu-latest
steps:
@ -140,7 +165,7 @@ jobs:
- name: Deps
run: |
sudo apt-get update
sudo apt-get install ccache apksigner -y
sudo apt-get install glslang-tools ccache apksigner -y
- name: Build
run: ./.ci/android/build.sh
- name: Copy and sign artifacts
@ -168,7 +193,7 @@ jobs:
TRANSIFEX_API_TOKEN: ${{ secrets.TRANSIFEX_API_TOKEN }}
release:
runs-on: ubuntu-latest
needs: [build, android, macos, source]
needs: [build, android, macos, source, windows]
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- uses: actions/download-artifact@v3

12
.gitmodules vendored
View File

@ -58,3 +58,15 @@
[submodule "sdl2"]
path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL
[submodule "vulkan-headers"]
path = externals/vulkan-headers
url = https://github.com/KhronosGroup/Vulkan-Headers
[submodule "glslang"]
path = externals/glslang
url = https://github.com/KhronosGroup/glslang
[submodule "glm"]
path = externals/glm
url = https://github.com/g-truc/glm
[submodule "sirit"]
path = externals/sirit
url = https://github.com/GPUCode/sirit

View File

@ -1,15 +1,15 @@
# CMake 3.12 required for 20 to be a valid value for CXX_STANDARD
cmake_minimum_required(VERSION 3.12)
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15)
# Don't override the warning flags in MSVC:
cmake_policy(SET CMP0092 NEW)
# Enforce new LTO setting
cmake_policy(SET CMP0069 NEW)
endif()
cmake_minimum_required(VERSION 3.15)
# Don't override the warning flags in MSVC:
cmake_policy(SET CMP0092 NEW)
# Enforce new LTO setting
cmake_policy(SET CMP0069 NEW)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
include(GNUInstallDirs)
include(CMakeDependentOption)
project(citra LANGUAGES C CXX ASM)
@ -43,6 +43,8 @@ CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_FFMPEG "Download bundled FFmpeg binarie
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder (preferred over FFmpeg)" ON "WIN32" OFF)
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ON "MINGW" OFF)
@ -51,6 +53,23 @@ option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)
CMAKE_DEPENDENT_OPTION(ENABLE_FDK "Use FDK AAC decoder" OFF "NOT ENABLE_FFMPEG_AUDIO_DECODER;NOT ENABLE_MF" OFF)
if (CITRA_USE_PRECOMPILED_HEADERS)
if (MSVC AND CCACHE)
# buildcache does not properly cache PCH files, leading to compilation errors.
# See https://github.com/mbitsnbites/buildcache/discussions/230
message(WARNING "Buildcache does not properly support Precompiled Headers. Disabling PCH")
set(CITRA_USE_PRECOMPILED_HEADERS OFF)
endif()
if(APPLE)
message(WARNING "Precompiled Headers currently do not work on Apple. Disabling PCH")
set(CITRA_USE_PRECOMPILED_HEADERS OFF)
endif()
endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
message(STATUS "Using Precompiled Headers.")
set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
endif()
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@ -158,6 +177,8 @@ message(STATUS "Target architecture: ${ARCHITECTURE}")
# Configure C++ standard
# ===========================
# boost asio's concept usage doesn't play nicely with some compilers yet.
add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -174,8 +195,8 @@ find_package(Threads REQUIRED)
if (ENABLE_QT)
if (CITRA_USE_BUNDLED_QT)
if (MSVC_VERSION GREATER_EQUAL 1930 AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.15.5-msvc2022_64)
if (MSVC_VERSION GREATER_EQUAL 1920 AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.15.7-msvc2019_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.")
endif()
@ -295,13 +316,15 @@ if (CLANG_FORMAT)
set(SRCS ${PROJECT_SOURCE_DIR}/src)
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
if (WIN32)
add_custom_target(clang-format
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
COMMENT ${CCOMMENT})
elseif(MINGW)
if(MINGW)
add_custom_target(clang-format
COMMAND find `cygpath -u ${SRCS}` -iname *.h -o -iname *.cpp -o -iname *.mm | xargs `cygpath -u ${CLANG_FORMAT}` -i
COMMENT ${CCOMMENT})
else()
add_custom_target(clang-format
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
COMMENT ${CCOMMENT})
endif()
else()
add_custom_target(clang-format
COMMAND find ${SRCS} -iname *.h -o -iname *.cpp -o -iname *.mm | xargs ${CLANG_FORMAT} -i

View File

@ -13,6 +13,6 @@
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="colorful">
<file>style.qss</file>
<file alias="style.qss">../default/style.qss</file>
</qresource>
</RCC>

View File

@ -1,4 +0,0 @@
/*
This file is intentionally left blank.
We do not want to apply any stylesheet for colorful, only icons.
*/

View File

@ -1,33 +1,22 @@
<RCC>
<qresource prefix="icons/default">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="256x256/citra.png">icons/256x256/citra.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="default">
<file>style.qss</file>
</qresource>
</RCC>

13
dist/qt_themes/default/style.qss vendored Normal file
View File

@ -0,0 +1,13 @@
QPushButton#GraphicsAPIStatusBarButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
min-width: 60px;
min-height: 20px;
}
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}

View File

@ -522,13 +522,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
border-width: 1px;
border-color: #54575B;
border-style: solid;
padding: 6px 4px;
border: 1px solid #54575B;
border-radius: 2px;
padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
min-height: 13px;
background-color: #232629;
}
@ -1237,3 +1236,17 @@ QPlainTextEdit:disabled {
TouchScreenPreview {
qproperty-dotHighlightColor: #3daee9;
}
QPushButton#GraphicsAPIStatusBarButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
min-width: 60px;
min-height: 20px;
}
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}

View File

@ -60,6 +60,19 @@ endif()
# Glad
add_subdirectory(glad)
# glslang
set(SKIP_GLSLANG_INSTALL ON)
set(ENABLE_GLSLANG_BINARIES OFF)
set(ENABLE_SPVREMAPPER OFF)
set(ENABLE_CTEST OFF)
add_subdirectory(glslang)
# Sirit
add_subdirectory(sirit)
# glm
add_subdirectory(glm)
# inih
add_subdirectory(inih)
@ -154,3 +167,12 @@ if(ANDROID)
add_subdirectory(libyuv)
target_include_directories(yuv INTERFACE ./libyuv/include)
endif()
# VMA
add_library(vma INTERFACE)
target_include_directories(vma INTERFACE ./vma)
# vulkan-headers
add_library(vulkan-headers INTERFACE)
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)

View File

@ -22,7 +22,7 @@ function(windows_copy_files TARGET SOURCE_DIR DEST_DIR)
# cmake adds an extra check for command success which doesn't work too well with robocopy
# so trick it into thinking the command was successful with the || cmd /c "exit /b 0"
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND if not exist ${DEST_DIR} mkdir ${DEST_DIR} 2> nul
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR}
COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0"
)
endfunction()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
externals/glm vendored Submodule

Submodule externals/glm added at cc98465e35

1
externals/glslang vendored Submodule

Submodule externals/glslang added at c0cf8ad876

View File

@ -834,7 +834,7 @@ struct MicroProfile
inline int MicroProfileLogType(MicroProfileLogEntry Index)
{
return ((MP_LOG_BEGIN_MASK & Index)>>62) & 0x3;
return (int)(((MP_LOG_BEGIN_MASK & Index)>>62) & 0x3ULL);
}
inline uint64_t MicroProfileLogTimerIndex(MicroProfileLogEntry Index)
@ -847,7 +847,7 @@ inline MicroProfileLogEntry MicroProfileMakeLogIndex(uint64_t nBegin, MicroProfi
MicroProfileLogEntry Entry = (nBegin<<62) | ((0x3fff&nToken)<<48) | (MP_LOG_TICK_MASK&nTick);
int t = MicroProfileLogType(Entry);
uint64_t nTimerIndex = MicroProfileLogTimerIndex(Entry);
MP_ASSERT(t == nBegin);
MP_ASSERT(static_cast<uint64_t>(t) == nBegin);
MP_ASSERT(nTimerIndex == (nToken&0x3fff));
return Entry;
@ -881,12 +881,12 @@ T MicroProfileMax(T a, T b)
inline int64_t MicroProfileMsToTick(float fMs, int64_t nTicksPerSecond)
{
return (int64_t)(fMs*0.001f*nTicksPerSecond);
return (int64_t)(fMs*0.001f*(float)nTicksPerSecond);
}
inline float MicroProfileTickToMsMultiplier(int64_t nTicksPerSecond)
{
return 1000.f / nTicksPerSecond;
return 1000.f / (float)nTicksPerSecond;
}
inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t)
@ -902,8 +902,10 @@ inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t)
#include <windows.h>
#define snprintf _snprintf
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#endif
int64_t MicroProfileTicksPerSecondCpu()
{
static int64_t nTicksPerSecond = 0;
@ -929,14 +931,14 @@ typedef void* (*MicroProfileThreadFunc)(void*);
#ifndef _WIN32
typedef pthread_t MicroProfileThread;
void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
{
pthread_attr_t Attr;
int r = pthread_attr_init(&Attr);
MP_ASSERT(r == 0);
pthread_create(pThread, &Attr, Func, 0);
}
void MicroProfileThreadJoin(MicroProfileThread* pThread)
inline void MicroProfileThreadJoin(MicroProfileThread* pThread)
{
int r = pthread_join(*pThread, 0);
MP_ASSERT(r == 0);
@ -953,11 +955,11 @@ DWORD _stdcall ThreadTrampoline(void* pFunc)
return static_cast<DWORD>(reinterpret_cast<uint64_t>(F(0)));
}
void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
{
*pThread = CreateThread(0, 0, ThreadTrampoline, Func, 0, 0);
}
void MicroProfileThreadJoin(MicroProfileThread* pThread)
inline void MicroProfileThreadJoin(MicroProfileThread* pThread)
{
WaitForSingleObject(*pThread, INFINITE);
CloseHandle(*pThread);
@ -1154,7 +1156,7 @@ inline void MicroProfileSetThreadLog(MicroProfileThreadLog* pLog)
pthread_setspecific(g_MicroProfileThreadLogKey, pLog);
}
#else
MicroProfileThreadLog* MicroProfileGetThreadLog()
inline MicroProfileThreadLog* MicroProfileGetThreadLog()
{
return g_MicroProfileThreadLog;
}
@ -1270,7 +1272,7 @@ MicroProfileToken MicroProfileFindToken(const char* pGroup, const char* pName)
return MICROPROFILE_INVALID_TOKEN;
}
uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
inline uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
{
for(uint32_t i = 0; i < S.nGroupCount; ++i)
{
@ -1299,7 +1301,7 @@ uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
return nGroupIndex;
}
void MicroProfileRegisterGroup(const char* pGroup, const char* pCategory, uint32_t nColor)
inline void MicroProfileRegisterGroup(const char* pGroup, const char* pCategory, uint32_t nColor)
{
int nCategoryIndex = -1;
for(uint32_t i = 0; i < S.nCategoryCount; ++i)
@ -1465,7 +1467,7 @@ void MicroProfileGpuLeave(MicroProfileToken nToken_, uint64_t nTickStart)
}
}
void MicroProfileContextSwitchPut(MicroProfileContextSwitch* pContextSwitch)
inline void MicroProfileContextSwitchPut(MicroProfileContextSwitch* pContextSwitch)
{
if(S.nRunning || pContextSwitch->nTicks <= S.nPauseTicks)
{
@ -1579,10 +1581,10 @@ void MicroProfileFlip()
pFramePut->nFrameStartCpu = MP_TICK();
pFramePut->nFrameStartGpu = (uint32_t)MicroProfileGpuInsertTimeStamp();
if(pFrameNext->nFrameStartGpu != (uint64_t)-1)
if(static_cast<uint64_t>(pFrameNext->nFrameStartGpu) != (uint64_t)-1)
pFrameNext->nFrameStartGpu = MicroProfileGpuGetTimeStamp((uint32_t)pFrameNext->nFrameStartGpu);
if(pFrameCurrent->nFrameStartGpu == (uint64_t)-1)
if(static_cast<uint64_t>(pFrameCurrent->nFrameStartGpu) == (uint64_t)-1)
pFrameCurrent->nFrameStartGpu = pFrameNext->nFrameStartGpu + 1;
uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu;
@ -1746,10 +1748,10 @@ void MicroProfileFlip()
}
}
}
for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i)
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
pLog->nGroupTicks[i] += nGroupTicks[i];
pFrameGroup[i] += nGroupTicks[i];
pLog->nGroupTicks[j] += nGroupTicks[j];
pFrameGroup[j] += nGroupTicks[j];
}
pLog->nStackPos = nStackPos;
}
@ -1917,7 +1919,7 @@ void MicroProfileSetEnableAllGroups(bool bEnableAllGroups)
S.nAllGroupsWanted = bEnableAllGroups ? 1 : 0;
}
void MicroProfileEnableCategory(const char* pCategory, bool bEnabled)
inline void MicroProfileEnableCategory(const char* pCategory, bool bEnabled)
{
int nCategoryIndex = -1;
for(uint32_t i = 0; i < S.nCategoryCount; ++i)
@ -2027,7 +2029,7 @@ void MicroProfileForceDisableGroup(const char* pGroup, MicroProfileTokenType Typ
}
void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, float* pTotal, uint32_t nSize)
inline void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, float* pTotal, uint32_t nSize)
{
for(uint32_t i = 0; i < S.nTotalTimers && i < nSize; ++i)
{
@ -3332,7 +3334,7 @@ bool MicroProfileIsLocalThread(uint32_t nThreadId)
#endif
#else
bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;}
bool MicroProfileIsLocalThread([[maybe_unused]] uint32_t nThreadId) { return false; }
void MicroProfileStopContextSwitchTrace(){}
void MicroProfileStartContextSwitchTrace(){}
@ -3580,7 +3582,7 @@ int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu)
#undef S
#ifdef _WIN32
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -354,7 +354,7 @@ void MicroProfileInitUI()
if(!bInitialized)
{
bInitialized = true;
memset(&g_MicroProfileUI, 0, sizeof(g_MicroProfileUI));
g_MicroProfileUI = {};
UI.nActiveMenu = UINT32_MAX;
UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f;
UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f;
@ -845,8 +845,8 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
MicroProfile& S = *MicroProfileGet();
MP_DEBUG_DUMP_RANGE();
int nY = nBaseY - UI.nOffsetY;
int64_t nNumBoxes = 0;
int64_t nNumLines = 0;
[[maybe_unused]] int64_t nNumBoxes = 0;
[[maybe_unused]] int64_t nNumLines = 0;
uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;
MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent];
@ -1988,7 +1988,7 @@ const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
else
{
nIndex = nIndex-1;
if(nIndex < UI.GroupMenuCount)
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
{
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
static char buffer[MICROPROFILE_NAME_MAX_LEN+32];
@ -2135,7 +2135,7 @@ const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
case 1: return "--";
default:
nIndex -= 2;
if(nIndex < UI.nCustomCount)
if(static_cast<uint32_t>(nIndex) < UI.nCustomCount)
{
return UI.Custom[nIndex].pName;
}
@ -2185,7 +2185,7 @@ void MicroProfileUIClickGroups(int nIndex)
else
{
nIndex -= 1;
if(nIndex < UI.GroupMenuCount)
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
{
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
if(Item.nIsCategory)

View File

@ -39,9 +39,9 @@ set(SDL_JOYSTICK ON CACHE BOOL "")
set(SDL_HAPTIC OFF CACHE BOOL "")
set(SDL_HIDAPI ON CACHE BOOL "")
set(SDL_POWER OFF CACHE BOOL "")
set(SDL_THREADS ON CACHE BOOL "")
set(SDL_TIMERS ON CACHE BOOL "")
set(SDL_FILE ON CACHE BOOL "")
set(SDL_THREADS ON CACHE BOOL "")
set(SDL_LOADSO ON CACHE BOOL "")
set(SDL_CPUINFO OFF CACHE BOOL "")
set(SDL_FILESYSTEM OFF CACHE BOOL "")

1
externals/sirit vendored Submodule

Submodule externals/sirit added at f0b6bbe55b

19670
externals/vma/vk_mem_alloc.h vendored Normal file

File diff suppressed because it is too large Load Diff

1
externals/vulkan-headers vendored Submodule

View File

@ -46,11 +46,21 @@ if (MSVC)
# Warnings
/W3
/we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled
/we4101 # 'identifier': unreferenced local variable
/we4265 # 'class': class has virtual functions, but destructor is not virtual
/we4267 # 'var': conversion from 'size_t' to 'type', possible loss of data
/we4388 # signed/unsigned mismatch
/we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
/we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
/we4555 # Expression has no effect; expected expression with side-effect
/we4834 # Discarding return value of function with 'nodiscard' attribute
/we5038 # data member 'member1' will be initialized after data member 'member2'
)
else()
add_compile_options(
/MP
/Zo
/permissive-
/EHsc
/volatile:iso
@ -64,17 +74,26 @@ if (MSVC)
# Warnings
/W3
/we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled
/we4101 # 'identifier': unreferenced local variable
/we4265 # 'class': class has virtual functions, but destructor is not virtual
/we4267 # 'var': conversion from 'size_t' to 'type', possible loss of data
/we4388 # signed/unsigned mismatch
/we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
/we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
/we4555 # Expression has no effect; expected expression with side-effect
/we4834 # Discarding return value of function with 'nodiscard' attribute
/we5038 # data member 'member1' will be initialized after data member 'member2'
)
endif()
# Since MSVC's debugging information is not very deterministic, so we have to disable it
# when using ccache or other caching tools
if (NOT CITRA_USE_CCACHE)
add_compile_options(
/Zi
/Zo
)
if (CITRA_USE_CCACHE OR CITRA_USE_PRECOMPILED_HEADERS)
# Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
add_compile_options(/Z7)
else()
add_compile_options(/Zi)
endif()
# /GS- - No stack buffer overflow checks
@ -103,6 +122,7 @@ else()
if (MINGW)
add_definitions(-DMINGW_HAS_SECURE_API)
add_compile_options("-Wa,-mbig-obj")
if (COMPILE_WITH_DWARF)
add_compile_options("-gdwarf")
endif()

View File

@ -30,7 +30,8 @@
android:supportsRtl="true"
android:isGame="true"
android:banner="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
android:requestLegacyExternalStorage="true"
android:debuggable="true">
<activity
android:name="org.citra.citra_emu.ui.main.MainActivity"

View File

@ -192,11 +192,15 @@ public final class SettingsFragmentPresenter {
Setting language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE);
Setting systemClock = systemSection.getSetting(SettingsFile.KEY_INIT_CLOCK);
Setting dateTime = systemSection.getSetting(SettingsFile.KEY_INIT_TIME);
Setting pluginLoader = systemSection.getSetting(SettingsFile.KEY_PLUGIN_LOADER);
Setting allowPluginLoader = systemSection.getSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER);
sl.add(new SingleChoiceSetting(SettingsFile.KEY_REGION_VALUE, Settings.SECTION_SYSTEM, R.string.emulated_region, 0, R.array.regionNames, R.array.regionValues, -1, region));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_LANGUAGE, Settings.SECTION_SYSTEM, R.string.emulated_language, 0, R.array.languageNames, R.array.languageValues, 1, language));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_INIT_CLOCK, Settings.SECTION_SYSTEM, R.string.init_clock, R.string.init_clock_description, R.array.systemClockNames, R.array.systemClockValues, 0, systemClock));
sl.add(new DateTimeSetting(SettingsFile.KEY_INIT_TIME, Settings.SECTION_SYSTEM, R.string.init_time, R.string.init_time_description, "2000-01-01 00:00:01", dateTime));
sl.add(new CheckBoxSetting(SettingsFile.KEY_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.plugin_loader, R.string.plugin_loader_description, false, pluginLoader));
sl.add(new CheckBoxSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.allow_plugin_loader, R.string.allow_plugin_loader_description, true, allowPluginLoader));
}
private void addCameraSettings(ArrayList<SettingsItem> sl) {

View File

@ -78,6 +78,8 @@ public final class SettingsFile {
public static final String KEY_IS_NEW_3DS = "is_new_3ds";
public static final String KEY_REGION_VALUE = "region_value";
public static final String KEY_LANGUAGE = "language";
public static final String KEY_PLUGIN_LOADER = "plugin_loader";
public static final String KEY_ALLOW_PLUGIN_LOADER = "allow_plugin_loader";
public static final String KEY_INIT_CLOCK = "init_clock";
public static final String KEY_INIT_TIME = "init_time";

View File

@ -8,7 +8,7 @@ import org.citra.citra_emu.CitraApplication;
public class EmulationMenuSettings {
private static SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext());
// These must match what is defined in src/core/settings.h
// These must match what is defined in src/common/settings.h
public static final int LayoutOption_Default = 0;
public static final int LayoutOption_SingleScreen = 1;
public static final int LayoutOption_LargeScreen = 2;

View File

@ -19,6 +19,8 @@ add_library(citra-android SHARED
default_ini.h
emu_window/emu_window.cpp
emu_window/emu_window.h
emu_window/emu_window_vk.cpp
emu_window/emu_window_vk.h
game_info.cpp
game_info.h
game_settings.cpp

View File

@ -11,10 +11,10 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/service.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "jni/camera/ndk_camera.h"
@ -114,7 +114,13 @@ void Config::ReadValues() {
sdl2_config->GetString("Premium", "texture_filter_name", "none");
// Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", true);
Settings::values.graphics_api =
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 2));
Settings::values.async_command_recording =
sdl2_config->GetBoolean("Renderer", "async_command_recording", true);
Settings::values.spirv_shader_gen =
sdl2_config->GetBoolean("Renderer", "spirv_shader_gen", true);
Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "renderer_debug", false);
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
Settings::values.shaders_accurate_mul =
@ -139,9 +145,9 @@ void Config::ReadValues() {
Settings::values.factor_3d =
static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
std::string default_shader = "none (builtin)";
if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
default_shader = "dubois (builtin)";
else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced)
else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced)
default_shader = "horizontal (builtin)";
Settings::values.pp_shader_name =
sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
@ -186,9 +192,9 @@ void Config::ReadValues() {
sdl2_config->GetBoolean("Utility", "preload_textures", false);
// Audio
Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false);
Settings::values.enable_dsp_lle_multithread =
sdl2_config->GetBoolean("Audio", "enable_dsp_lle_multithread", false);
Settings::values.audio_emulation =
static_cast<Settings::AudioEmulation>(sdl2_config->GetInteger(
"Audio", "audio_emulation", static_cast<int>(Settings::AudioEmulation::HLE)));
Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
@ -229,6 +235,10 @@ void Config::ReadValues() {
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch())
.count();
}
Settings::values.plugin_loader_enabled =
sdl2_config->GetBoolean("System", "plugin_loader", false);
Settings::values.allow_plugin_loader =
sdl2_config->GetBoolean("System", "allow_plugin_loader", true);
// Camera
using namespace Service::CAM;

View File

@ -281,6 +281,11 @@ init_clock =
# Note: 3DS can only handle times later then Jan 1 2000
init_time =
# Plugin loader state, if enabled plugins will be loaded from the SD card.
# You can also set if homebrew apps are allowed to enable the plugin loader
plugin_loader =
allow_plugin_loader =
[Camera]
# Which camera engine to use for the right outer camera
# blank: a dummy camera that always returns black image

View File

@ -11,7 +11,7 @@
#include <glad/glad.h>
#include "common/logging/log.h"
#include "core/settings.h"
#include "common/settings.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window.h"
#include "jni/id_cache.h"

View File

@ -0,0 +1,176 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <cstdlib>
#include <string>
#include <android/native_window_jni.h>
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window_vk.h"
#include "jni/id_cache.h"
#include "jni/input_manager.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
static bool IsPortraitMode() {
return JNI_FALSE != IDCache::GetEnvForThread()->CallStaticBooleanMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
}
static void UpdateLandscapeScreenLayout() {
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(IDCache::GetEnvForThread()->CallStaticIntMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout()));
}
void EmuWindow_Android_Vulkan::OnSurfaceChanged(ANativeWindow* surface) {
render_window = surface;
StopPresenting();
}
bool EmuWindow_Android_Vulkan::OnTouchEvent(int x, int y, bool pressed) {
if (pressed) {
return TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
}
TouchReleased();
return true;
}
void EmuWindow_Android_Vulkan::OnTouchMoved(int x, int y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
}
void EmuWindow_Android_Vulkan::OnFramebufferSizeChanged() {
UpdateLandscapeScreenLayout();
const bool is_portrait_mode{IsPortraitMode()};
const int bigger{window_width > window_height ? window_width : window_height};
const int smaller{window_width < window_height ? window_width : window_height};
if (is_portrait_mode) {
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
} else {
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
}
}
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(ANativeWindow* surface) {
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android_Vulkan");
if (!surface) {
LOG_CRITICAL(Frontend, "surface is nullptr");
return;
}
Network::Init();
host_window = surface;
CreateWindowSurface();
if (core_context = CreateSharedContext(); !core_context) {
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
return;
}
OnFramebufferSizeChanged();
}
bool EmuWindow_Android_Vulkan::CreateWindowSurface() {
if (!host_window) {
return true;
}
window_info.type = Frontend::WindowSystemType::Android;
window_info.render_surface = host_window;
return true;
}
void EmuWindow_Android_Vulkan::DestroyWindowSurface() {
/*if (!egl_surface) {
return;
}
if (eglGetCurrentSurface(EGL_DRAW) == egl_surface) {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
if (!eglDestroySurface(egl_display, egl_surface)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
egl_surface = EGL_NO_SURFACE;*/
}
void EmuWindow_Android_Vulkan::DestroyContext() {
/*if (!egl_context) {
return;
}
if (eglGetCurrentContext() == egl_context) {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
if (!eglDestroyContext(egl_display, egl_context)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
if (!eglTerminate(egl_display)) {
LOG_CRITICAL(Frontend, "eglTerminate() failed");
}
egl_context = EGL_NO_CONTEXT;
egl_display = EGL_NO_DISPLAY;*/
}
EmuWindow_Android_Vulkan::~EmuWindow_Android_Vulkan() {
DestroyWindowSurface();
DestroyContext();
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_Vulkan::CreateSharedContext() const {
return std::make_unique<SharedContext_Android>();
}
void EmuWindow_Android_Vulkan::StopPresenting() {
/*if (presenting_state == PresentingState::Running) {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}*/
presenting_state = PresentingState::Stopped;
}
void EmuWindow_Android_Vulkan::TryPresenting() {
if (presenting_state != PresentingState::Running) {
if (presenting_state == PresentingState::Initial) {
/*eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);*/
presenting_state = PresentingState::Running;
} else {
return;
}
}
/*eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0);
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(0);
eglSwapBuffers(egl_display, egl_surface);
}*/
}
void EmuWindow_Android_Vulkan::PollEvents() {
if (!render_window) {
return;
}
host_window = render_window;
render_window = nullptr;
DestroyWindowSurface();
CreateWindowSurface();
OnFramebufferSizeChanged();
presenting_state = PresentingState::Initial;
}
void EmuWindow_Android_Vulkan::MakeCurrent() {
core_context->MakeCurrent();
}
void EmuWindow_Android_Vulkan::DoneCurrent() {
core_context->DoneCurrent();
}

View File

@ -0,0 +1,59 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "core/frontend/emu_window.h"
struct ANativeWindow;
class SharedContext_Android : public Frontend::GraphicsContext {};
class EmuWindow_Android_Vulkan : public Frontend::EmuWindow {
public:
EmuWindow_Android_Vulkan(ANativeWindow* surface);
~EmuWindow_Android_Vulkan();
void Present();
/// Called by the onSurfaceChanges() method to change the surface
void OnSurfaceChanged(ANativeWindow* surface);
/// Handles touch event that occur.(Touched or released)
bool OnTouchEvent(int x, int y, bool pressed);
/// Handles movement of touch pointer
void OnTouchMoved(int x, int y);
void PollEvents() override;
void MakeCurrent() override;
void DoneCurrent() override;
void TryPresenting();
void StopPresenting();
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
private:
void OnFramebufferSizeChanged();
bool CreateWindowSurface();
void DestroyWindowSurface();
void DestroyContext();
ANativeWindow* render_window{};
ANativeWindow* host_window{};
int window_width{1080};
int window_height{2220};
std::unique_ptr<Frontend::GraphicsContext> core_context;
enum class PresentingState {
Initial,
Running,
Stopped,
};
PresentingState presenting_state{};
};

View File

@ -2,7 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/settings.h"
#include "common/settings.h"
namespace GameSettings {

View File

@ -6,7 +6,7 @@
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "core/settings.h"
#include "common/settings.h"
#include "jni/applets/mii_selector.h"
#include "jni/applets/swkbd.h"
#include "jni/camera/still_image_camera.h"
@ -156,7 +156,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// Initialize Logger
Log::Filter log_filter;
log_filter.ParseFilterString(Settings::values.log_filter);
log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
Log::SetGlobalFilter(log_filter);
Log::AddBackend(std::make_unique<Log::LogcatBackend>());
FileUtil::CreateFullPath(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));

View File

@ -17,23 +17,22 @@
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/camera/factory.h"
#include "core/frontend/mic.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/savestate.h"
#include "core/settings.h"
#include "jni/android_common/android_common.h"
#include "jni/applets/mii_selector.h"
#include "jni/applets/swkbd.h"
#include "jni/camera/ndk_camera.h"
#include "jni/camera/still_image_camera.h"
#include "jni/config.h"
#include "jni/emu_window/emu_window.h"
#include "jni/emu_window/emu_window_vk.h"
#include "jni/game_info.h"
#include "jni/game_settings.h"
#include "jni/id_cache.h"
@ -49,7 +48,7 @@ namespace {
ANativeWindow* s_surf;
std::unique_ptr<EmuWindow_Android> window;
std::unique_ptr<EmuWindow_Android_Vulkan> window;
std::atomic<bool> stop_run{true};
std::atomic<bool> pause_emulation{false};
@ -147,7 +146,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
return Core::System::ResultStatus::ErrorLoader;
}
window = std::make_unique<EmuWindow_Android>(s_surf);
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf);
Core::System& system{Core::System::GetInstance()};
@ -238,7 +237,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
}
} else {
// Ensure no audio bleeds out while game is paused
const float volume = Settings::values.volume;
const float volume = Settings::values.volume.GetValue();
SCOPE_EXIT({ Settings::values.volume = volume; });
Settings::values.volume = 0;

View File

@ -37,6 +37,10 @@
<string name="init_time_description">Si el \"Tipo del reloj del sistema\" está en \"Reloj emulado\", ésto cambia la fecha y hora de inicio.</string>
<string name="emulated_region">Región emulada</string>
<string name="emulated_language">Idioma emulado</string>
<string name="plugin_loader">Activar \"3GX Plugin Loader\"</string>
<string name="plugin_loader_description">Carga \"3GX plugins\" de la SD emulada si están disponibles.</string>
<string name="allow_plugin_loader">Permiter que apps cambien el estado del \"plugin loader\"</string>
<string name="allow_plugin_loader_description">Permite a las aplicaciones homebrew activar el \"plugin loader\" incluso si está desactivado.</string>
<!-- Camera settings strings -->
<string name="inner_camera">Cámara interior</string>

View File

@ -51,6 +51,10 @@
<string name="init_time_description">If the \"System clock type\" setting is set to \"Simulated clock\", this changes the fixed date and time to start at.</string>
<string name="emulated_region">Emulated region</string>
<string name="emulated_language">Emulated language</string>
<string name="plugin_loader">Enable 3GX Plugin Loader</string>
<string name="plugin_loader_description">Loads 3GX plugins from the emulated SD if they are available.</string>
<string name="allow_plugin_loader">Allow apps to change plugin loader state</string>
<string name="allow_plugin_loader_description">Allow homebrew apps to enable the plugin loader even when it is disabled.</string>
<!-- Camera settings strings -->
<string name="inner_camera">Inner Camera</string>

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath 'com.android.tools.build:gradle:7.3.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -23,6 +23,7 @@ add_library(audio_core STATIC
interpolate.cpp
interpolate.h
null_sink.h
precompiled_headers.h
sink.h
sink_details.cpp
sink_details.h
@ -89,3 +90,7 @@ if(ENABLE_CUBEB)
target_link_libraries(audio_core PRIVATE cubeb)
target_compile_definitions(audio_core PUBLIC HAVE_CUBEB)
endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
endif()

View File

@ -7,17 +7,18 @@
#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "common/assert.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/dumping/backend.h"
#include "core/settings.h"
namespace AudioCore {
DspInterface::DspInterface() = default;
DspInterface::~DspInterface() = default;
void DspInterface::SetSink(const std::string& sink_id, const std::string& audio_device) {
sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) {
sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
Settings::values.audio_device_id.GetValue());
sink->SetCallback(
[this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); });
time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
@ -86,7 +87,7 @@ void DspInterface::OutputCallback(s16* buffer, std::size_t num_frames) {
// Implementation of the hardware volume slider
// A cubic curve is used to approximate a linear change in human-perceived loudness
const float linear_volume = std::clamp(Settings::values.volume, 0.0f, 1.0f);
const float linear_volume = std::clamp(Settings::Volume(), 0.0f, 1.0f);
if (linear_volume != 1.0) {
const float volume_scale_factor = linear_volume * linear_volume * linear_volume;
for (std::size_t i = 0; i < num_frames; i++) {

View File

@ -94,7 +94,7 @@ public:
virtual void UnloadComponent() = 0;
/// Select the sink to use based on sink id.
void SetSink(const std::string& sink_id, const std::string& audio_device);
void SetSink(std::string_view sink_id, std::string_view audio_device);
/// Get the current sink
Sink& GetSink();
/// Enable/Disable audio stretching.

View File

@ -0,0 +1,7 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_precompiled_headers.h"

View File

@ -10,6 +10,7 @@ add_executable(citra
emu_window/emu_window_sdl2.h
lodepng_image_interface.cpp
lodepng_image_interface.h
precompiled_headers.h
resource.h
)
@ -30,3 +31,7 @@ if (MSVC)
include(CopyCitraSDLDeps)
copy_citra_SDL_deps(citra)
endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
target_precompile_headers(citra PRIVATE precompiled_headers.h)
endif()

View File

@ -7,17 +7,7 @@
#include <regex>
#include <string>
#include <thread>
// This needs to be included before getopt.h because the latter #defines symbols used by it
#include "common/microprofile.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#endif
#include "citra/config.h"
#include "citra/emu_window/emu_window_sdl2.h"
#include "citra/lodepng_image_interface.h"
@ -25,23 +15,20 @@
#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/dumping/backend.h"
#include "core/file_sys/cia_container.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/loader/loader.h"
#include "core/movie.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
@ -52,6 +39,11 @@
#endif
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
extern "C" {
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
@ -104,35 +96,35 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
break;
case Network::RoomMember::Error::CouldNotConnect:
LOG_ERROR(Network, "Error: Could not connect");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::NameCollision:
LOG_ERROR(
Network,
"You tried to use the same nickname as another user that is connected to the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::MacCollision:
LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
"connected to the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::ConsoleIdCollision:
LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::WrongPassword:
LOG_ERROR(Network, "Room replied with: Wrong password");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::WrongVersion:
LOG_ERROR(Network,
"You are using a different version than the room you are trying to connect to");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::RoomIsFull:
LOG_ERROR(Network, "The room is full");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::HostKicked:
LOG_ERROR(Network, "You have been kicked by the host");
@ -140,6 +132,9 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
case Network::RoomMember::Error::HostBanned:
LOG_ERROR(Network, "You have been banned by the host");
break;
default:
LOG_ERROR(Network, "Unknown network error: {}", error);
break;
}
}
@ -172,7 +167,7 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
static void InitializeLogging() {
Log::Filter log_filter(Log::Level::Debug);
log_filter.ParseFilterString(Settings::values.log_filter);
log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
Log::SetGlobalFilter(log_filter);
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
@ -190,8 +185,8 @@ int main(int argc, char** argv) {
Common::DetachedTasks detached_tasks;
Config config;
int option_index = 0;
bool use_gdbstub = Settings::values.use_gdbstub;
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
bool use_gdbstub = Settings::values.use_gdbstub.GetValue();
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port.GetValue());
std::string movie_record;
std::string movie_record_author;
std::string movie_play;
@ -358,11 +353,23 @@ int main(int argc, char** argv) {
// Register generic image interface
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
Frontend::ScopeAcquireContext scope(*emu_window);
Core::System& system = Core::System::GetInstance();
EmuWindow_SDL2::InitializeSDL2();
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
const bool use_secondary_window{Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::SeparateWindows};
const auto secondary_window =
use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
const auto scope = emu_window->Acquire();
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);
Settings::LogSettings();
Core::System& system = Core::System::GetInstance();
const Core::System::ResultStatus load_result{
system.Load(*emu_window, filepath, secondary_window.get())};
switch (load_result) {
case Core::System::ResultStatus::ErrorGetLoader:
@ -430,7 +437,12 @@ int main(int argc, char** argv) {
system.VideoDumper().StartDumping(dump_video, layout);
}
std::thread render_thread([&emu_window] { emu_window->Present(); });
std::thread main_render_thread([&emu_window] { emu_window->Present(); });
std::thread secondary_render_thread([&secondary_window] {
if (secondary_window) {
secondary_window->Present();
}
});
std::atomic_bool stop_run;
system.Renderer().Rasterizer()->LoadDiskResources(
@ -439,7 +451,11 @@ int main(int argc, char** argv) {
total);
});
while (emu_window->IsOpen()) {
const auto secondary_is_open = [&secondary_window] {
// if the secondary window isn't created, it shouldn't affect the main loop
return secondary_window ? secondary_window->IsOpen() : true;
};
while (emu_window->IsOpen() && secondary_is_open()) {
const auto result = system.RunLoop();
switch (result) {
@ -453,13 +469,21 @@ int main(int argc, char** argv) {
break;
}
}
render_thread.join();
emu_window->RequestClose();
if (secondary_window) {
secondary_window->RequestClose();
}
main_render_thread.join();
secondary_render_thread.join();
Core::Movie::GetInstance().Shutdown();
if (system.VideoDumper().IsDumping()) {
system.VideoDumper().StopDumping();
}
Network::Shutdown();
InputCommon::Shutdown();
system.Shutdown();
detached_tasks.WaitForAllTasks();

View File

@ -13,9 +13,9 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "core/frontend/mic.h"
#include "core/hle/service/service.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "network/network_settings.h"
@ -109,7 +109,8 @@ void Config::ReadValues() {
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
// Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
Settings::values.graphics_api =
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 0));
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
#ifdef __APPLE__
@ -128,24 +129,23 @@ void Config::ReadValues() {
sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_frame_limit_alternate =
sdl2_config->GetBoolean("Renderer", "use_frame_limit_alternate", false);
Settings::values.frame_limit_alternate =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit_alternate", 200));
Settings::values.use_vsync_new =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1));
Settings::values.texture_filter_name =
sdl2_config->GetString("Renderer", "texture_filter_name", "none");
Settings::values.mono_render_option = static_cast<Settings::MonoRenderOption>(
sdl2_config->GetInteger("Renderer", "mono_render_option", 0));
Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
sdl2_config->GetInteger("Renderer", "render_3d", 0));
Settings::values.factor_3d =
static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
std::string default_shader = "none (builtin)";
if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
default_shader = "dubois (builtin)";
else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced)
else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() ==
Settings::StereoRenderOption::ReverseInterlaced)
default_shader = "horizontal (builtin)";
Settings::values.pp_shader_name =
sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
@ -186,9 +186,8 @@ void Config::ReadValues() {
sdl2_config->GetBoolean("Utility", "preload_textures", false);
// Audio
Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false);
Settings::values.enable_dsp_lle_multithread =
sdl2_config->GetBoolean("Audio", "enable_dsp_lle_multithread", false);
Settings::values.audio_emulation = static_cast<Settings::AudioEmulation>(
sdl2_config->GetInteger("Audio", "audio_emulation", 0));
Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
@ -240,6 +239,58 @@ void Config::ReadValues() {
.count();
}
{
constexpr const char* default_init_time_offset = "0 00:00:00";
std::string offset_string =
sdl2_config->GetString("System", "init_time_offset", default_init_time_offset);
size_t sep_index = offset_string.find(' ');
if (sep_index == std::string::npos) {
LOG_ERROR(Config, "Failed to parse init_time_offset. Using 0 00:00:00");
offset_string = default_init_time_offset;
sep_index = offset_string.find(' ');
}
std::string day_string = offset_string.substr(0, sep_index);
long long days = 0;
try {
days = std::stoll(day_string);
} catch (std::logic_error&) {
LOG_ERROR(Config, "Failed to parse days in init_time_offset. Using 0");
days = 0;
}
long long days_in_seconds = days * 86400;
std::tm t;
t.tm_sec = 0;
t.tm_min = 0;
t.tm_hour = 0;
t.tm_mday = 1;
t.tm_mon = 0;
t.tm_year = 100;
t.tm_isdst = 0;
std::istringstream string_stream(offset_string.substr(sep_index + 1));
string_stream >> std::get_time(&t, "%H:%M:%S");
if (string_stream.fail()) {
LOG_ERROR(Config,
"Failed to parse hours, minutes and seconds in init_time_offset. 00:00:00");
}
auto time_offset =
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch();
auto secs = std::chrono::duration_cast<std::chrono::seconds>(time_offset).count();
Settings::values.init_time_offset = static_cast<long long>(secs) + days_in_seconds;
}
// Camera
using namespace Service::CAM;
Settings::values.camera_name[OuterRightCamera] =

View File

@ -166,6 +166,10 @@ render_3d =
# 0 - 100: Intensity. 0 (default)
factor_3d =
# Change Default Eye to Render When in Monoscopic Mode
# 0 (default): Left, 1: Right
mono_render_option =
# The name of the post processing shader to apply.
# Loaded from shaders if render_3d is off or side by side.
# Loaded from shaders/anaglyph if render_3d is anaglyph
@ -178,7 +182,11 @@ filter_mode =
[Layout]
# Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side
# 0 (default): Default Top Bottom Screen
# 1: Single Screen Only
# 2: Large Screen Small Screen
# 3: Side by Side
# 4: Separate Windows
layout_option =
# Toggle custom layout (using the settings below) on or off.

View File

@ -12,9 +12,9 @@
#include "citra/emu_window/emu_window_sdl2.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/3ds.h"
#include "core/core.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
@ -135,19 +135,11 @@ void EmuWindow_SDL2::Fullscreen() {
SDL_MaximizeWindow(render_window);
}
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
// Initialize the window
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError());
exit(1);
}
InputCommon::Init();
Network::Init();
SDL_SetMainReady();
if (Settings::values.use_gles) {
const bool is_opengles =
Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGLES;
if (is_opengles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
@ -201,7 +193,8 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
exit(1);
}
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
render_window_id = SDL_GetWindowID(render_window);
auto gl_load_func = is_opengles ? gladLoadGLES2Loader : gladLoadGLLoader;
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
@ -211,19 +204,26 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);
Settings::LogSettings();
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
core_context.reset();
Network::Shutdown();
InputCommon::Shutdown();
SDL_GL_DeleteContext(window_context);
SDL_Quit();
}
void EmuWindow_SDL2::InitializeSDL2() {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError());
exit(1);
}
InputCommon::Init();
Network::Init();
SDL_SetMainReady();
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
return std::make_unique<SharedContext_SDL2>();
}
@ -240,7 +240,7 @@ void EmuWindow_SDL2::Present() {
SDL_GL_MakeCurrent(render_window, window_context);
SDL_GL_SetSwapInterval(1);
while (IsOpen()) {
VideoCore::g_renderer->TryPresent(100);
VideoCore::g_renderer->TryPresent(100, is_secondary);
SDL_GL_SwapWindow(render_window);
}
SDL_GL_MakeCurrent(render_window, nullptr);
@ -248,9 +248,14 @@ void EmuWindow_SDL2::Present() {
void EmuWindow_SDL2::PollEvents() {
SDL_Event event;
std::vector<SDL_Event> other_window_events;
// SDL_PollEvent returns 0 when there are no more events in the event queue
while (SDL_PollEvent(&event)) {
if (event.window.windowID != render_window_id) {
other_window_events.push_back(event);
continue;
}
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
@ -299,16 +304,13 @@ void EmuWindow_SDL2::PollEvents() {
break;
}
}
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
const auto title =
fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
results.emulation_speed * 100.0f);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
for (auto& e : other_window_events) {
// This is a somewhat hacky workaround to re-emit window events meant for another window
// since SDL_PollEvent() is global but we poll events per window.
SDL_PushEvent(&e);
}
if (!is_secondary) {
UpdateFramerateCounter();
}
}
@ -323,3 +325,16 @@ void EmuWindow_SDL2::DoneCurrent() {
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}
void EmuWindow_SDL2::UpdateFramerateCounter() {
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
const auto title =
fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
results.emulation_speed * 100.0f);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
}
}

View File

@ -29,9 +29,11 @@ private:
class EmuWindow_SDL2 : public Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2(bool fullscreen);
explicit EmuWindow_SDL2(bool fullscreen, bool is_secondary);
~EmuWindow_SDL2();
static void InitializeSDL2();
void Present();
/// Polls window events
@ -88,12 +90,18 @@ private:
/// Called when a configuration change affects the minimal size of the window
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
/// Called when polling to update framerate
void UpdateFramerateCounter();
/// Is the window still open?
bool is_open = true;
/// Internal SDL2 render window
SDL_Window* render_window;
/// Internal SDL2 window ID
int render_window_id{};
/// Fake hidden window for the core context
SDL_Window* dummy_window;

View File

@ -0,0 +1,7 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_precompiled_headers.h"

View File

@ -38,6 +38,8 @@ add_executable(citra-qt
configuration/config.cpp
configuration/config.h
configuration/configure.ui
configuration/configuration_shared.cpp
configuration/configuration_shared.h
configuration/configure_audio.cpp
configuration/configure_audio.h
configuration/configure_audio.ui
@ -67,6 +69,9 @@ add_executable(citra-qt
configuration/configure_motion_touch.cpp
configuration/configure_motion_touch.h
configuration/configure_motion_touch.ui
configuration/configure_per_game.cpp
configuration/configure_per_game.h
configuration/configure_per_game.ui
configuration/configure_storage.cpp
configuration/configure_storage.h
configuration/configure_storage.ui
@ -159,6 +164,7 @@ add_executable(citra-qt
multiplayer/state.cpp
multiplayer/state.h
multiplayer/validation.h
precompiled_headers.h
uisettings.cpp
uisettings.h
qt_image_interface.cpp
@ -244,6 +250,8 @@ if (APPLE)
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
target_sources(citra-qt PRIVATE
applesurfacehelper.h
applesurfacehelper.mm
macos_authorization.h
macos_authorization.mm
)
@ -260,9 +268,13 @@ endif()
create_target_directory_groups(citra-qt)
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia)
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (NOT WIN32)
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_compile_definitions(citra-qt PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
@ -314,3 +326,7 @@ if (MSVC)
copy_citra_FFmpeg_deps(citra-qt)
endif()
endif()
if (CITRA_USE_PRECOMPILED_HEADERS)
target_precompile_headers(citra-qt PRIVATE precompiled_headers.h)
endif()

View File

@ -0,0 +1,11 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace AppleSurfaceHelper {
void* GetSurfaceLayer(void* surface);
} // namespace AppleSurfaceHelper

View File

@ -0,0 +1,16 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#import <Cocoa/Cocoa.h>
#include "citra_qt/applesurfacehelper.h"
namespace AppleSurfaceHelper {
void* GetSurfaceLayer(void* surface) {
NSView* view = static_cast<NSView*>(surface);
return view.layer;
}
} // AppleSurfaceHelper

View File

@ -54,12 +54,12 @@ QtKeyboardDialog::QtKeyboardDialog(QWidget* parent, QtKeyboard* keyboard_)
case ButtonConfig::None:
break;
}
connect(buttons, &QDialogButtonBox::accepted, this, [=] { Submit(); });
connect(buttons, &QDialogButtonBox::rejected, this, [=] {
connect(buttons, &QDialogButtonBox::accepted, this, [this] { Submit(); });
connect(buttons, &QDialogButtonBox::rejected, this, [this] {
button = QtKeyboard::cancel_id;
accept();
});
connect(buttons, &QDialogButtonBox::helpRequested, this, [=] {
connect(buttons, &QDialogButtonBox::helpRequested, this, [this] {
button = QtKeyboard::forgot_id;
accept();
});

View File

@ -6,67 +6,75 @@
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMessageBox>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_4_3_Core>
#include <QOpenGLExtraFunctions>
#include <fmt/format.h>
#include "citra_qt/bootmanager.h"
#include "citra_qt/main.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/3ds.h"
#include "core/core.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/perf_stats.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#if defined(__APPLE__)
#include "citra_qt/applesurfacehelper.h"
#endif
#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
EmuThread::~EmuThread() = default;
static GMainWindow* GetMainWindow() {
for (QWidget* w : qApp->topLevelWidgets()) {
const auto widgets = qApp->topLevelWidgets();
for (QWidget* w : widgets) {
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
return main;
}
}
return nullptr;
}
void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread");
Frontend::ScopeAcquireContext scope(core_context);
const auto scope = core_context.Acquire();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
Core::System& system = Core::System::GetInstance();
system.Renderer().Rasterizer()->LoadDiskResources(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
emit HideLoadingScreen();
core_context.MakeCurrent();
if (Core::System::GetInstance().frame_limiter.IsFrameAdvancing()) {
if (system.frame_limiter.IsFrameAdvancing()) {
// Usually the loading screen is hidden after the first frame is drawn. In this case
// we hide it immediately as we need to wait for user input to start the emulation.
emit HideLoadingScreen();
Core::System::GetInstance().frame_limiter.WaitOnce();
system.frame_limiter.WaitOnce();
}
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step.
bool was_active = false;
Core::System& system = Core::System::GetInstance();
while (!stop_run) {
if (running) {
if (!was_active)
@ -111,91 +119,246 @@ void EmuThread::run() {
#endif
}
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
event_handler(event_handler) {
class OpenGLSharedContext : public Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
format.setVersion(4, 4);
format.setProfile(QSurfaceFormat::CoreProfile);
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
this->setFormat(format);
auto format = share_context->format();
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new.GetValue() : 0);
context->setShareContext(shared_context);
context->setScreen(this->screen());
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
context->create();
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
setSurfaceType(QWindow::OpenGLSurface);
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
}
OpenGLWindow::~OpenGLWindow() {
~OpenGLSharedContext() {
context->doneCurrent();
}
void OpenGLWindow::Present() {
if (!isExposed())
return;
context->makeCurrent(this);
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(100);
}
context->swapBuffers(this);
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
f->glFinish();
QWindow::requestUpdate();
}
bool OpenGLWindow::event(QEvent* event) {
switch (event->type()) {
case QEvent::UpdateRequest:
void SwapBuffers() override {
context->swapBuffers(surface);
}
void MakeCurrent() override {
// We can't track the current state of the underlying context in this wrapper class because
// Qt may make the underlying context not current for one reason or another. In particular,
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
// Instead of always just making the context current (which does not have any caching to
// check if the underlying context is already current) we can check for the current context
// in the thread local data by calling `currentContext()` and checking if its ours.
if (QOpenGLContext::currentContext() != context.get()) {
context->makeCurrent(surface);
}
}
void DoneCurrent() override {
context->doneCurrent();
}
QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
};
class DummyContext : public Frontend::GraphicsContext {};
class RenderWidget : public QWidget {
public:
RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
}
virtual ~RenderWidget() = default;
virtual void Present() {}
void paintEvent(QPaintEvent* event) override {
Present();
return true;
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::FocusAboutToChange:
case QEvent::Enter:
case QEvent::Leave:
case QEvent::Wheel:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::InputMethodQuery:
case QEvent::TouchCancel:
return QCoreApplication::sendEvent(event_handler, event);
case QEvent::Drop:
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
return true;
case QEvent::DragEnter:
case QEvent::DragMove:
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
return true;
default:
return QWindow::event(event);
update();
}
void resizeEvent(QResizeEvent* ev) override {
render_window->resize(ev->size());
render_window->OnFramebufferSizeChanged();
}
void keyPressEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->PressKey(event->key());
}
void keyReleaseEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void mousePressEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchBeginEvent
const auto pos{event->pos()};
if (event->button() == Qt::LeftButton) {
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void mouseMoveEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchUpdateEvent
const auto pos{event->pos()};
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void mouseReleaseEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchEndEvent
if (event->button() == Qt::LeftButton)
render_window->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
std::pair<unsigned, unsigned> GetSize() const {
return std::make_pair(width(), height());
}
QPaintEngine* paintEngine() const override {
return nullptr;
}
private:
GRenderWindow* render_window;
};
class OpenGLRenderWidget : public RenderWidget {
public:
explicit OpenGLRenderWidget(GRenderWindow* parent, bool is_secondary)
: RenderWidget(parent), is_secondary(is_secondary) {
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
}
void SetContext(std::unique_ptr<OpenGLSharedContext>&& context_) {
context = std::move(context_);
}
void Present() override {
if (!isVisible()) {
return;
}
if (!Core::System::GetInstance().IsPoweredOn()) {
return;
}
context->MakeCurrent();
const auto f = context->GetShareContext()->extraFunctions();
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
VideoCore::g_renderer->TryPresent(100, is_secondary);
context->SwapBuffers();
f->glFinish();
}
private:
std::unique_ptr<OpenGLSharedContext> context{};
bool is_secondary;
};
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
}
};
static Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
return Frontend::WindowSystemType::Windows;
else if (platform_name == QStringLiteral("xcb"))
return Frontend::WindowSystemType::X11;
else if (platform_name == QStringLiteral("wayland"))
return Frontend::WindowSystemType::Wayland;
else if (platform_name == QStringLiteral("cocoa"))
return Frontend::WindowSystemType::MacOS;
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
return Frontend::WindowSystemType::Windows;
}
void OpenGLWindow::exposeEvent(QExposeEvent* event) {
QWindow::requestUpdate();
QWindow::exposeEvent(event);
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
// Our Win32 Qt external doesn't have the private API.
#if defined(WIN32)
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#elif defined(__APPLE__)
wsi.render_surface =
window ? AppleSurfaceHelper::GetSurfaceLayer(reinterpret_cast<void*>(window->winId()))
: nullptr;
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
if (wsi.type == Frontend::WindowSystemType::Wayland)
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
else
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#endif
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
return wsi;
}
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
: QWidget(parent_), emu_thread(emu_thread) {
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
: QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
@ -205,7 +368,6 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
InputCommon::Init();
this->setMouseTracking(true);
@ -213,16 +375,14 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
}
GRenderWindow::~GRenderWindow() = default;
void GRenderWindow::MakeCurrent() {
core_context->MakeCurrent();
main_context->MakeCurrent();
}
void GRenderWindow::DoneCurrent() {
core_context->DoneCurrent();
main_context->DoneCurrent();
}
void GRenderWindow::PollEvents() {
@ -380,6 +540,12 @@ bool GRenderWindow::event(QEvent* event) {
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
InputCommon::GetKeyboard()->ReleaseAllKeys();
has_focus = false;
}
void GRenderWindow::focusInEvent(QFocusEvent* event) {
QWidget::focusInEvent(event);
has_focus = true;
}
void GRenderWindow::resizeEvent(QResizeEvent* event) {
@ -387,43 +553,80 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
OnFramebufferSizeChanged();
}
void GRenderWindow::InitRenderTarget() {
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
if (graphics_api == Settings::GraphicsAPI::OpenGL ||
graphics_api == Settings::GraphicsAPI::OpenGLES) {
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
// Bind the shared contexts to the main surface in case the backend wants to take over
// presentation
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
child_widget->windowHandle());
}
return std::make_unique<DummyContext>();
}
bool GRenderWindow::InitRenderTarget() {
ReleaseRenderTarget();
{
// Create a dummy render widget so that Qt
// places the render window at the correct position.
const RenderWidget dummy_widget{this};
}
first_frame = false;
GMainWindow* parent = GetMainWindow();
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
child_window->create();
child_widget = createWindowContainer(child_window, this);
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
case Settings::GraphicsAPI::OpenGL:
case Settings::GraphicsAPI::OpenGLES:
if (!InitializeOpenGL()) {
return false;
}
break;
case Settings::GraphicsAPI::Vulkan:
if (!InitializeVulkan()) {
return false;
}
break;
}
// Update the Window System information with the new render target
window_info = GetWindowSystemInfo(child_widget->windowHandle());
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
layout()->addWidget(child_widget);
// Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1);
core_context = CreateSharedContext();
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
BackupGeometry();
return true;
}
void GRenderWindow::ReleaseRenderTarget() {
if (child_widget) {
layout()->removeWidget(child_widget);
delete child_widget;
child_widget->deleteLater();
child_widget = nullptr;
}
main_context.reset();
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
if (res_scale == 0)
res_scale = VideoCore::GetResolutionScaleFactor();
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
VideoCore::RequestScreenshot(
screenshot_image.bits(),
[=] {
[this, screenshot_path] {
const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
@ -438,6 +641,29 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
setMinimumSize(minimal_size.first, minimal_size.second);
}
bool GRenderWindow::InitializeOpenGL() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this, is_secondary);
child_widget = child;
child_widget->windowHandle()->create();
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
main_context = context;
child->SetContext(
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
return true;
}
bool GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
return true;
}
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread;
}
@ -449,31 +675,3 @@ void GRenderWindow::OnEmulationStopping() {
void GRenderWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
}
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
return std::make_unique<GLContext>(QOpenGLContext::globalShareContext());
}
GLContext::GLContext(QOpenGLContext* shared_context)
: context(std::make_unique<QOpenGLContext>(shared_context->parent())),
surface(std::make_unique<QOffscreenSurface>(nullptr)) {
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(0);
context->setShareContext(shared_context);
context->setFormat(format);
context->create();
surface->setParent(shared_context->parent());
surface->setFormat(format);
surface->create();
}
void GLContext::MakeCurrent() {
context->makeCurrent(surface.get());
}
void GLContext::DoneCurrent() {
context->doneCurrent();
}

View File

@ -27,19 +27,6 @@ namespace VideoCore {
enum class LoadCallbackStage;
}
class GLContext : public Frontend::GraphicsContext {
public:
explicit GLContext(QOpenGLContext* shared_context);
void MakeCurrent() override;
void DoneCurrent() override;
private:
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> surface;
};
class EmuThread final : public QThread {
Q_OBJECT
@ -126,29 +113,11 @@ signals:
void HideLoadingScreen();
};
class OpenGLWindow : public QWindow {
Q_OBJECT
public:
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
~OpenGLWindow();
void Present();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
private:
std::unique_ptr<QOpenGLContext> context;
QWidget* event_handler;
};
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
Q_OBJECT
public:
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
GRenderWindow(QWidget* parent, EmuThread* emu_thread, bool is_secondary);
~GRenderWindow() override;
// EmuWindow implementation.
@ -178,14 +147,20 @@ public:
bool event(QEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
bool HasFocus() const {
return has_focus;
}
void InitRenderTarget();
bool InitRenderTarget();
/// Destroy the previous run's child_widget which should also destroy the child_window
void ReleaseRenderTarget();
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
public slots:
void OnEmulationStarting(EmuThread* emu_thread);
@ -205,30 +180,31 @@ signals:
void MouseActivity();
private:
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
std::unique_ptr<GraphicsContext> core_context;
QByteArray geometry;
/// Native window handle that backs this presentation widget
QWindow* child_window = nullptr;
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
/// put the child_window into a widget then add it to the layout. This child_widget can be
/// parented to GRenderWindow and use Qt's lifetime system
QWidget* child_widget = nullptr;
bool InitializeOpenGL();
bool InitializeVulkan();
EmuThread* emu_thread;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
// should instead be shared from
std::shared_ptr<Frontend::GraphicsContext> main_context;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
QByteArray geometry;
QWidget* child_widget = nullptr;
bool first_frame = false;
bool has_focus = false;
protected:
void showEvent(QShowEvent* event) override;

View File

@ -19,25 +19,31 @@ namespace Camera {
QList<QVideoFrame::PixelFormat> QtCameraSurface::supportedPixelFormats(
[[maybe_unused]] QAbstractVideoBuffer::HandleType handleType) const {
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_ARGB32 << QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32 << QVideoFrame::Format_RGB24 << QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555 << QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32 << QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32 << QVideoFrame::Format_BGR24 << QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555 << QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444 << QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444 << QVideoFrame::Format_YUV420P << QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY << QVideoFrame::Format_YUYV << QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21 << QVideoFrame::Format_IMC1 << QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3 << QVideoFrame::Format_IMC4 << QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16 << QVideoFrame::Format_Jpeg << QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng; // Supporting all the formats
<< QVideoFrame::Format_RGB32 << QVideoFrame::Format_RGB24
<< QVideoFrame::Format_ARGB32_Premultiplied << QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_RGB565 << QVideoFrame::Format_RGB555
<< QVideoFrame::Format_Jpeg
// the following formats are supported via Qt internal conversions
<< QVideoFrame::Format_ARGB8565_Premultiplied << QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied << QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24 << QVideoFrame::Format_BGR565 << QVideoFrame::Format_BGR555
<< QVideoFrame::Format_AYUV444 << QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P << QVideoFrame::Format_YV12 << QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV << QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21; // Supporting all the QImage convertible formats, ordered by
// QImage decoding performance
}
bool QtCameraSurface::present(const QVideoFrame& frame) {
if (!frame.isValid()) {
return false;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QMutexLocker locker(&mutex);
// In Qt 5.15, the image is already flipped
current_frame = frame.image();
locker.unlock();
#else
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
@ -46,6 +52,7 @@ bool QtCameraSurface::present(const QVideoFrame& frame) {
current_frame = image.mirrored(true, true);
locker.unlock();
cloneFrame.unmap();
#endif
return true;
}
@ -199,6 +206,7 @@ void QtMultimediaCameraHandler::StartCamera() {
camera->setViewfinderSettings(settings);
camera->start();
started = true;
paused = false;
}
bool QtMultimediaCameraHandler::CameraAvailable() const {
@ -210,13 +218,14 @@ void QtMultimediaCameraHandler::StopCameras() {
for (auto& handler : handlers) {
if (handler && handler->started) {
handler->StopCamera();
handler->paused = true;
}
}
}
void QtMultimediaCameraHandler::ResumeCameras() {
for (auto& handler : handlers) {
if (handler && handler->started) {
if (handler && handler->paused) {
handler->StartCamera();
}
}
@ -228,6 +237,7 @@ void QtMultimediaCameraHandler::ReleaseHandlers() {
for (std::size_t i = 0; i < handlers.size(); i++) {
status[i] = false;
handlers[i]->started = false;
handlers[i]->paused = false;
}
}

View File

@ -90,6 +90,7 @@ private:
QtCameraSurface camera_surface{};
QCameraViewfinderSettings settings;
bool started = false;
bool paused = false; // was previously started but was paused, to be resumed
static std::array<std::shared_ptr<QtMultimediaCameraHandler>, 3> handlers;
static std::array<bool, 3> status;

View File

@ -37,7 +37,7 @@ CheatDialog::CheatDialog(QWidget* parent)
connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
connect(ui->buttonSave, &QPushButton::clicked,
connect(ui->buttonSave, &QPushButton::clicked, this,
[this] { SaveCheat(ui->tableCheats->currentRow()); });
connect(ui->buttonDelete, &QPushButton::clicked, this, &CheatDialog::OnDeleteCheat);
@ -91,7 +91,7 @@ bool CheatDialog::SaveCheat(int row) {
}
// Check if the cheat lines are valid
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, QString::SkipEmptyParts);
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, Qt::SkipEmptyParts);
for (int i = 0; i < code_lines.size(); ++i) {
Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
if (cheat_line.valid)
@ -190,8 +190,9 @@ void CheatDialog::OnDeleteCheat() {
if (newly_created) {
newly_created = false;
} else {
Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow());
Core::System::GetInstance().CheatEngine().SaveCheatFile();
auto& cheat_engine = Core::System::GetInstance().CheatEngine();
cheat_engine.RemoveCheat(ui->tableCheats->currentRow());
cheat_engine.SaveCheatFile();
}
LoadCheats();

View File

@ -17,17 +17,15 @@
#include "network/network.h"
#include "network/network_settings.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
FileUtil::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload();
Config::Config(const std::string& config_name, ConfigType config_type) : type{config_type} {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
}
Config::~Config() {
if (global) {
Save();
}
}
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
@ -58,7 +56,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 23> default_hotkeys{
const std::array<UISettings::Shortcut, 24> default_hotkeys{
{{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral("\\"), Qt::ApplicationShortcut}},
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@ -70,6 +68,7 @@ const std::array<UISettings::Shortcut, 23> default_hotkeys{
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
@ -84,46 +83,176 @@ const std::array<UISettings::Shortcut, 23> default_hotkeys{
{QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+D"), Qt::ApplicationShortcut}}}};
// clang-format on
void Config::Initialize(const std::string& config_name) {
const std::string fs_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir);
const std::string config_file = fmt::format("{}.ini", config_name);
switch (type) {
case ConfigType::GlobalConfig:
qt_config_loc = fmt::format("{}/{}", fs_config_loc, config_file);
break;
case ConfigType::PerGameConfig:
qt_config_loc = fmt::format("{}/custom/{}", fs_config_loc, config_file);
break;
}
FileUtil::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload();
}
/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their
* usages later in this file. This allows explicit definition of some types that don't work
* nicely with the general version.
*/
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
// can it implicitly convert a QVariant back to a {std::,Q}string
template <>
void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const auto default_value = QString::fromStdString(setting.GetDefault());
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
setting.SetValue(default_value.toStdString());
} else {
setting.SetValue(qt_config->value(name, default_value).toString().toStdString());
}
}
template <typename Type, bool ranged>
void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type default_value = setting.GetDefault();
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
setting.SetValue(default_value);
} else {
QVariant value{};
if constexpr (std::is_enum_v<Type>) {
using TypeU = std::underlying_type_t<Type>;
value = qt_config->value(name, static_cast<TypeU>(default_value));
setting.SetValue(static_cast<Type>(value.value<TypeU>()));
} else {
value = qt_config->value(name, QVariant::fromValue(default_value));
setting.SetValue(value.value<Type>());
}
}
}
template <typename Type, bool ranged>
void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
if (global || !use_global) {
QVariant value{};
if constexpr (std::is_enum_v<Type>) {
using TypeU = std::underlying_type_t<Type>;
value = QVariant::fromValue<TypeU>(static_cast<TypeU>(setting.GetDefault()));
setting.SetValue(static_cast<Type>(ReadSetting(name, value).value<TypeU>()));
} else {
value = QVariant::fromValue<Type>(setting.GetDefault());
setting.SetValue(ReadSetting(name, value).value<Type>());
}
}
}
template <>
void Config::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
if (global || !use_global) {
const QString default_value = QString::fromStdString(setting.GetDefault());
setting.SetValue(
ReadSetting(name, QVariant::fromValue(default_value)).toString().toStdString());
}
}
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
template <>
void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, QString::fromStdString(value));
}
template <typename Type, bool ranged>
void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
if constexpr (std::is_enum_v<Type>) {
qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
} else {
qt_config->setValue(name, QVariant::fromValue(value));
}
}
template <typename Type, bool ranged>
void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type& value = setting.GetValue(global);
if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
}
if (global || !setting.UsingGlobal()) {
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
if constexpr (std::is_enum_v<Type>) {
qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
} else {
qt_config->setValue(name, QVariant::fromValue(value));
}
}
}
template <>
void Config::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue(global);
if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
}
if (global || !setting.UsingGlobal()) {
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, QString::fromStdString(value));
}
}
void Config::ReadValues() {
if (global) {
ReadControlValues();
ReadCoreValues();
ReadRendererValues();
ReadLayoutValues();
ReadAudioValues();
ReadCameraValues();
ReadDataStorageValues();
ReadSystemValues();
ReadMiscellaneousValues();
ReadDebuggingValues();
ReadWebServiceValues();
ReadVideoDumpingValues();
ReadUIValues();
ReadUtilityValues();
}
ReadUIValues();
ReadCoreValues();
ReadRendererValues();
ReadLayoutValues();
ReadAudioValues();
ReadSystemValues();
}
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
Settings::values.enable_dsp_lle = ReadSetting(QStringLiteral("enable_dsp_lle"), false).toBool();
Settings::values.enable_dsp_lle_multithread =
ReadSetting(QStringLiteral("enable_dsp_lle_multithread"), false).toBool();
Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
.toString()
.toStdString();
Settings::values.enable_audio_stretching =
ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
Settings::values.audio_device_id =
ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
.toString()
.toStdString();
Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
Settings::values.mic_input_type = static_cast<Settings::MicInputType>(
ReadSetting(QStringLiteral("mic_input_type"), 0).toInt());
Settings::values.mic_input_device =
ReadSetting(QStringLiteral("mic_input_device"),
QString::fromUtf8(Frontend::Mic::default_device_name))
.toString()
.toStdString();
ReadGlobalSetting(Settings::values.audio_emulation);
ReadGlobalSetting(Settings::values.enable_audio_stretching);
ReadGlobalSetting(Settings::values.volume);
if (global) {
ReadBasicSetting(Settings::values.sink_id);
ReadBasicSetting(Settings::values.audio_device_id);
ReadBasicSetting(Settings::values.mic_input_device);
ReadBasicSetting(Settings::values.mic_input_type);
}
qt_config->endGroup();
}
@ -280,11 +409,9 @@ void Config::ReadControlValues() {
void Config::ReadUtilityValues() {
qt_config->beginGroup(QStringLiteral("Utility"));
Settings::values.dump_textures = ReadSetting(QStringLiteral("dump_textures"), false).toBool();
Settings::values.custom_textures =
ReadSetting(QStringLiteral("custom_textures"), false).toBool();
Settings::values.preload_textures =
ReadSetting(QStringLiteral("preload_textures"), false).toBool();
ReadBasicSetting(Settings::values.dump_textures);
ReadBasicSetting(Settings::values.custom_textures);
ReadBasicSetting(Settings::values.preload_textures);
qt_config->endGroup();
}
@ -292,9 +419,11 @@ void Config::ReadUtilityValues() {
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
Settings::values.cpu_clock_percentage =
ReadSetting(QStringLiteral("cpu_clock_percentage"), 100).toInt();
ReadGlobalSetting(Settings::values.cpu_clock_percentage);
if (global) {
ReadBasicSetting(Settings::values.use_cpu_jit);
}
qt_config->endGroup();
}
@ -302,10 +431,9 @@ void Config::ReadCoreValues() {
void Config::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
ReadBasicSetting(Settings::values.use_virtual_sd);
ReadBasicSetting(Settings::values.use_custom_storage);
Settings::values.use_custom_storage =
ReadSetting(QStringLiteral("use_custom_storage"), false).toBool();
const std::string nand_dir =
ReadSetting(QStringLiteral("nand_directory"), QStringLiteral("")).toString().toStdString();
const std::string sdmc_dir =
@ -325,8 +453,10 @@ void Config::ReadDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
ReadBasicSetting(Settings::values.use_gdbstub);
ReadBasicSetting(Settings::values.gdbstub_port);
ReadBasicSetting(Settings::values.renderer_debug);
ReadBasicSetting(Settings::values.dump_command_buffers);
qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Service::service_module_map) {
@ -334,43 +464,38 @@ void Config::ReadDebuggingValues() {
Settings::values.lle_modules.emplace(service_module.name, use_lle);
}
qt_config->endGroup();
qt_config->endGroup();
}
void Config::ReadLayoutValues() {
qt_config->beginGroup(QStringLiteral("Layout"));
Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
ReadSetting(QStringLiteral("render_3d"), 0).toInt());
Settings::values.factor_3d = ReadSetting(QStringLiteral("factor_3d"), 0).toInt();
ReadGlobalSetting(Settings::values.render_3d);
ReadGlobalSetting(Settings::values.factor_3d);
Settings::values.pp_shader_name =
ReadSetting(QStringLiteral("pp_shader_name"),
(Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
ReadSetting(QStringLiteral("pp_shader_name"), (Settings::values.render_3d.GetValue() ==
Settings::StereoRenderOption::Anaglyph)
? QStringLiteral("dubois (builtin)")
: QStringLiteral("none (builtin)"))
.toString()
.toStdString();
Settings::values.filter_mode = ReadSetting(QStringLiteral("filter_mode"), true).toBool();
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt());
Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool();
Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool();
Settings::values.custom_layout = ReadSetting(QStringLiteral("custom_layout"), false).toBool();
Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt();
Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt();
Settings::values.custom_top_right =
ReadSetting(QStringLiteral("custom_top_right"), 400).toInt();
Settings::values.custom_top_bottom =
ReadSetting(QStringLiteral("custom_top_bottom"), 240).toInt();
Settings::values.custom_bottom_left =
ReadSetting(QStringLiteral("custom_bottom_left"), 40).toInt();
Settings::values.custom_bottom_top =
ReadSetting(QStringLiteral("custom_bottom_top"), 240).toInt();
Settings::values.custom_bottom_right =
ReadSetting(QStringLiteral("custom_bottom_right"), 360).toInt();
Settings::values.custom_bottom_bottom =
ReadSetting(QStringLiteral("custom_bottom_bottom"), 480).toInt();
ReadGlobalSetting(Settings::values.filter_mode);
ReadGlobalSetting(Settings::values.layout_option);
ReadGlobalSetting(Settings::values.swap_screen);
ReadGlobalSetting(Settings::values.upright_screen);
if (global) {
ReadBasicSetting(Settings::values.mono_render_option);
ReadBasicSetting(Settings::values.custom_layout);
ReadBasicSetting(Settings::values.custom_top_left);
ReadBasicSetting(Settings::values.custom_top_top);
ReadBasicSetting(Settings::values.custom_top_right);
ReadBasicSetting(Settings::values.custom_top_bottom);
ReadBasicSetting(Settings::values.custom_bottom_left);
ReadBasicSetting(Settings::values.custom_bottom_top);
ReadBasicSetting(Settings::values.custom_bottom_right);
ReadBasicSetting(Settings::values.custom_bottom_bottom);
}
qt_config->endGroup();
}
@ -378,10 +503,7 @@ void Config::ReadLayoutValues() {
void Config::ReadMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
Settings::values.log_filter =
ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info"))
.toString()
.toStdString();
ReadBasicSetting(Settings::values.log_filter);
qt_config->endGroup();
}
@ -431,13 +553,15 @@ void Config::ReadMultiplayerValues() {
void Config::ReadPathValues() {
qt_config->beginGroup(QStringLiteral("Paths"));
ReadGlobalSetting(UISettings::values.screenshot_path);
if (global) {
UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
UISettings::values.movie_record_path =
ReadSetting(QStringLiteral("movieRecordPath")).toString();
UISettings::values.movie_playback_path =
ReadSetting(QStringLiteral("moviePlaybackPath")).toString();
UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
UISettings::values.video_dumping_path =
ReadSetting(QStringLiteral("videoDumpingPath")).toString();
UISettings::values.game_dir_deprecated =
@ -471,6 +595,7 @@ void Config::ReadPathValues() {
}
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
}
qt_config->endGroup();
}
@ -478,38 +603,33 @@ void Config::ReadPathValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
Settings::values.use_hw_renderer =
ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
ReadGlobalSetting(Settings::values.physical_device);
ReadGlobalSetting(Settings::values.async_command_recording);
ReadGlobalSetting(Settings::values.spirv_shader_gen);
ReadGlobalSetting(Settings::values.graphics_api);
ReadGlobalSetting(Settings::values.use_hw_renderer);
ReadGlobalSetting(Settings::values.use_hw_shader);
#ifdef __APPLE__
// Hardware shader is broken on macos with Intel GPUs thanks to poor drivers.
// We still want to provide this option for test/development purposes, but disable it by
// default.
Settings::values.separable_shader =
ReadSetting(QStringLiteral("separable_shader"), false).toBool();
ReadGlobalSetting(Settings::values.separable_shader);
#endif
Settings::values.shaders_accurate_mul =
ReadSetting(QStringLiteral("shaders_accurate_mul"), true).toBool();
Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool();
Settings::values.use_disk_shader_cache =
ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool();
Settings::values.resolution_factor =
static_cast<u16>(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt());
Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
Settings::values.use_frame_limit_alternate =
ReadSetting(QStringLiteral("use_frame_limit_alternate"), false).toBool();
Settings::values.frame_limit_alternate =
ReadSetting(QStringLiteral("frame_limit_alternate"), 200).toInt();
ReadGlobalSetting(Settings::values.shaders_accurate_mul);
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.use_vsync_new);
ReadGlobalSetting(Settings::values.resolution_factor);
ReadGlobalSetting(Settings::values.frame_limit);
Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
Settings::values.texture_filter_name =
ReadSetting(QStringLiteral("texture_filter_name"), QStringLiteral("none"))
.toString()
.toStdString();
ReadGlobalSetting(Settings::values.texture_filter_name);
if (global) {
ReadBasicSetting(Settings::values.use_shader_jit);
}
qt_config->endGroup();
}
@ -517,7 +637,7 @@ void Config::ReadRendererValues() {
void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (auto [name, group, shortcut] : default_hotkeys) {
for (const auto& [name, group, shortcut] : default_hotkeys) {
auto [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
@ -536,14 +656,16 @@ void Config::ReadShortcutValues() {
void Config::ReadSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
Settings::values.is_new_3ds = ReadSetting(QStringLiteral("is_new_3ds"), true).toBool();
Settings::values.region_value =
ReadSetting(QStringLiteral("region_value"), Settings::REGION_VALUE_AUTO_SELECT).toInt();
Settings::values.init_clock = static_cast<Settings::InitClock>(
ReadSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::InitClock::SystemTime))
.toInt());
Settings::values.init_time =
ReadSetting(QStringLiteral("init_time"), 946681277ULL).toULongLong();
ReadGlobalSetting(Settings::values.is_new_3ds);
ReadGlobalSetting(Settings::values.region_value);
if (global) {
ReadBasicSetting(Settings::values.init_clock);
ReadBasicSetting(Settings::values.init_time);
ReadBasicSetting(Settings::values.init_time_offset);
ReadBasicSetting(Settings::values.plugin_loader_enabled);
ReadBasicSetting(Settings::values.allow_plugin_loader);
}
qt_config->endGroup();
}
@ -552,7 +674,7 @@ void Config::ReadSystemValues() {
// https://developers.google.com/media/vp9/live-encoding
const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QString{};
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QStringLiteral("");
void Config::ReadVideoDumpingValues() {
qt_config->beginGroup(QStringLiteral("VideoDumping"));
@ -594,39 +716,33 @@ void Config::ReadVideoDumpingValues() {
void Config::ReadUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
ReadPathValues();
if (global) {
UISettings::values.theme =
ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
.toString();
UISettings::values.enable_discord_presence =
ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
UISettings::values.screenshot_resolution_factor =
static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
ReadBasicSetting(UISettings::values.enable_discord_presence);
ReadBasicSetting(UISettings::values.screenshot_resolution_factor);
ReadUpdaterValues();
ReadUILayoutValues();
ReadUIGameListValues();
ReadPathValues();
ReadShortcutValues();
ReadMultiplayerValues();
UISettings::values.single_window_mode =
ReadSetting(QStringLiteral("singleWindowMode"), true).toBool();
UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool();
UISettings::values.display_titlebar =
ReadSetting(QStringLiteral("displayTitleBars"), true).toBool();
UISettings::values.show_filter_bar =
ReadSetting(QStringLiteral("showFilterBar"), true).toBool();
UISettings::values.show_status_bar =
ReadSetting(QStringLiteral("showStatusBar"), true).toBool();
UISettings::values.confirm_before_closing =
ReadSetting(QStringLiteral("confirmClose"), true).toBool();
UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
UISettings::values.pause_when_in_background =
ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
UISettings::values.hide_mouse =
ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();
ReadBasicSetting(UISettings::values.single_window_mode);
ReadBasicSetting(UISettings::values.fullscreen);
ReadBasicSetting(UISettings::values.display_titlebar);
ReadBasicSetting(UISettings::values.show_filter_bar);
ReadBasicSetting(UISettings::values.show_status_bar);
ReadBasicSetting(UISettings::values.confirm_before_closing);
ReadBasicSetting(UISettings::values.first_start);
ReadBasicSetting(UISettings::values.callout_flags);
ReadBasicSetting(UISettings::values.show_console);
ReadBasicSetting(UISettings::values.pause_when_in_background);
ReadBasicSetting(UISettings::values.hide_mouse);
}
qt_config->endGroup();
}
@ -634,36 +750,11 @@ void Config::ReadUIValues() {
void Config::ReadUIGameListValues() {
qt_config->beginGroup(QStringLiteral("GameList"));
auto icon_size = UISettings::GameListIconSize{
ReadSetting(QStringLiteral("iconSize"),
static_cast<int>(UISettings::GameListIconSize::LargeIcon))
.toInt()};
if (icon_size < UISettings::GameListIconSize::NoIcon ||
icon_size > UISettings::GameListIconSize::LargeIcon) {
icon_size = UISettings::GameListIconSize::LargeIcon;
}
UISettings::values.game_list_icon_size = icon_size;
UISettings::GameListText row_1 = UISettings::GameListText{
ReadSetting(QStringLiteral("row1"), static_cast<int>(UISettings::GameListText::TitleName))
.toInt()};
if (row_1 <= UISettings::GameListText::NoText || row_1 >= UISettings::GameListText::ListEnd) {
row_1 = UISettings::GameListText::TitleName;
}
UISettings::values.game_list_row_1 = row_1;
UISettings::GameListText row_2 = UISettings::GameListText{
ReadSetting(QStringLiteral("row2"), static_cast<int>(UISettings::GameListText::FileName))
.toInt()};
if (row_2 < UISettings::GameListText::NoText || row_2 >= UISettings::GameListText::ListEnd) {
row_2 = UISettings::GameListText::FileName;
}
UISettings::values.game_list_row_2 = row_2;
UISettings::values.game_list_hide_no_icon =
ReadSetting(QStringLiteral("hideNoIcon"), false).toBool();
UISettings::values.game_list_single_line_mode =
ReadSetting(QStringLiteral("singleLineMode"), false).toBool();
ReadBasicSetting(UISettings::values.game_list_icon_size);
ReadBasicSetting(UISettings::values.game_list_row_1);
ReadBasicSetting(UISettings::values.game_list_row_2);
ReadBasicSetting(UISettings::values.game_list_hide_no_icon);
ReadBasicSetting(UISettings::values.game_list_single_line_mode);
qt_config->endGroup();
}
@ -679,8 +770,7 @@ void Config::ReadUILayoutValues() {
ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
UISettings::values.microprofile_geometry =
ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
UISettings::values.microprofile_visible =
ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool();
ReadBasicSetting(UISettings::values.microprofile_visible);
qt_config->endGroup();
}
@ -688,10 +778,8 @@ void Config::ReadUILayoutValues() {
void Config::ReadUpdaterValues() {
qt_config->beginGroup(QStringLiteral("Updater"));
UISettings::values.check_for_update_on_start =
ReadSetting(QStringLiteral("check_for_update_on_start"), true).toBool();
UISettings::values.update_on_close =
ReadSetting(QStringLiteral("update_on_close"), false).toBool();
ReadBasicSetting(UISettings::values.check_for_update_on_start);
ReadBasicSetting(UISettings::values.update_on_close);
qt_config->endGroup();
}
@ -714,40 +802,38 @@ void Config::ReadWebServiceValues() {
}
void Config::SaveValues() {
if (global) {
SaveControlValues();
SaveCoreValues();
SaveRendererValues();
SaveLayoutValues();
SaveAudioValues();
SaveCameraValues();
SaveDataStorageValues();
SaveSystemValues();
SaveMiscellaneousValues();
SaveDebuggingValues();
SaveWebServiceValues();
SaveVideoDumpingValues();
SaveUIValues();
SaveUtilityValues();
}
SaveUIValues();
SaveCoreValues();
SaveRendererValues();
SaveLayoutValues();
SaveAudioValues();
SaveSystemValues();
}
void Config::SaveAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
WriteSetting(QStringLiteral("enable_dsp_lle"), Settings::values.enable_dsp_lle, false);
WriteSetting(QStringLiteral("enable_dsp_lle_multithread"),
Settings::values.enable_dsp_lle_multithread, false);
WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
QStringLiteral("auto"));
WriteSetting(QStringLiteral("enable_audio_stretching"),
Settings::values.enable_audio_stretching, true);
WriteSetting(QStringLiteral("output_device"),
QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
WriteSetting(QStringLiteral("mic_input_device"),
QString::fromStdString(Settings::values.mic_input_device),
QString::fromUtf8(Frontend::Mic::default_device_name));
WriteSetting(QStringLiteral("mic_input_type"),
static_cast<int>(Settings::values.mic_input_type), 0);
WriteGlobalSetting(Settings::values.audio_emulation);
WriteGlobalSetting(Settings::values.enable_audio_stretching);
WriteGlobalSetting(Settings::values.volume);
if (global) {
WriteBasicSetting(Settings::values.sink_id);
WriteBasicSetting(Settings::values.audio_device_id);
WriteBasicSetting(Settings::values.mic_input_device);
WriteBasicSetting(Settings::values.mic_input_type);
}
qt_config->endGroup();
}
@ -844,9 +930,9 @@ void Config::SaveControlValues() {
void Config::SaveUtilityValues() {
qt_config->beginGroup(QStringLiteral("Utility"));
WriteSetting(QStringLiteral("dump_textures"), Settings::values.dump_textures, false);
WriteSetting(QStringLiteral("custom_textures"), Settings::values.custom_textures, false);
WriteSetting(QStringLiteral("preload_textures"), Settings::values.preload_textures, false);
WriteBasicSetting(Settings::values.dump_textures);
WriteBasicSetting(Settings::values.custom_textures);
WriteBasicSetting(Settings::values.preload_textures);
qt_config->endGroup();
}
@ -854,9 +940,11 @@ void Config::SaveUtilityValues() {
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
WriteSetting(QStringLiteral("cpu_clock_percentage"), Settings::values.cpu_clock_percentage,
100);
WriteGlobalSetting(Settings::values.cpu_clock_percentage);
if (global) {
WriteBasicSetting(Settings::values.use_cpu_jit);
}
qt_config->endGroup();
}
@ -864,8 +952,8 @@ void Config::SaveCoreValues() {
void Config::SaveDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
WriteSetting(QStringLiteral("use_custom_storage"), Settings::values.use_custom_storage, false);
WriteBasicSetting(Settings::values.use_virtual_sd);
WriteBasicSetting(Settings::values.use_custom_storage);
WriteSetting(QStringLiteral("nand_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
QStringLiteral(""));
@ -881,8 +969,10 @@ void Config::SaveDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteBasicSetting(Settings::values.use_gdbstub);
WriteBasicSetting(Settings::values.gdbstub_port);
WriteBasicSetting(Settings::values.renderer_debug);
WriteBasicSetting(Settings::values.dump_command_buffers);
qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Settings::values.lle_modules) {
@ -896,27 +986,30 @@ void Config::SaveDebuggingValues() {
void Config::SaveLayoutValues() {
qt_config->beginGroup(QStringLiteral("Layout"));
WriteSetting(QStringLiteral("render_3d"), static_cast<int>(Settings::values.render_3d), 0);
WriteSetting(QStringLiteral("factor_3d"), Settings::values.factor_3d.load(), 0);
WriteGlobalSetting(Settings::values.render_3d);
WriteGlobalSetting(Settings::values.factor_3d);
WriteSetting(QStringLiteral("pp_shader_name"),
QString::fromStdString(Settings::values.pp_shader_name),
(Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
QString::fromStdString(Settings::values.pp_shader_name.GetValue()),
(Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
? QStringLiteral("dubois (builtin)")
: QStringLiteral("none (builtin)"));
WriteSetting(QStringLiteral("filter_mode"), Settings::values.filter_mode, true);
WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option));
WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false);
WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false);
WriteSetting(QStringLiteral("custom_layout"), Settings::values.custom_layout, false);
WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0);
WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0);
WriteSetting(QStringLiteral("custom_top_right"), Settings::values.custom_top_right, 400);
WriteSetting(QStringLiteral("custom_top_bottom"), Settings::values.custom_top_bottom, 240);
WriteSetting(QStringLiteral("custom_bottom_left"), Settings::values.custom_bottom_left, 40);
WriteSetting(QStringLiteral("custom_bottom_top"), Settings::values.custom_bottom_top, 240);
WriteSetting(QStringLiteral("custom_bottom_right"), Settings::values.custom_bottom_right, 360);
WriteSetting(QStringLiteral("custom_bottom_bottom"), Settings::values.custom_bottom_bottom,
480);
WriteGlobalSetting(Settings::values.filter_mode);
WriteGlobalSetting(Settings::values.layout_option);
WriteGlobalSetting(Settings::values.swap_screen);
WriteGlobalSetting(Settings::values.upright_screen);
if (global) {
WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_layout);
WriteBasicSetting(Settings::values.custom_top_left);
WriteBasicSetting(Settings::values.custom_top_top);
WriteBasicSetting(Settings::values.custom_top_right);
WriteBasicSetting(Settings::values.custom_top_bottom);
WriteBasicSetting(Settings::values.custom_bottom_left);
WriteBasicSetting(Settings::values.custom_bottom_top);
WriteBasicSetting(Settings::values.custom_bottom_right);
WriteBasicSetting(Settings::values.custom_bottom_bottom);
}
qt_config->endGroup();
}
@ -924,8 +1017,7 @@ void Config::SaveLayoutValues() {
void Config::SaveMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter),
QStringLiteral("*:Info"));
WriteBasicSetting(Settings::values.log_filter);
qt_config->endGroup();
}
@ -967,11 +1059,12 @@ void Config::SaveMultiplayerValues() {
void Config::SavePathValues() {
qt_config->beginGroup(QStringLiteral("Paths"));
WriteGlobalSetting(UISettings::values.screenshot_path);
if (global) {
WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
WriteSetting(QStringLiteral("movieRecordPath"), UISettings::values.movie_record_path);
WriteSetting(QStringLiteral("moviePlaybackPath"), UISettings::values.movie_playback_path);
WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
WriteSetting(QStringLiteral("videoDumpingPath"), UISettings::values.video_dumping_path);
qt_config->beginWriteArray(QStringLiteral("gamedirs"));
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
@ -984,6 +1077,7 @@ void Config::SavePathValues() {
qt_config->endArray();
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
}
qt_config->endGroup();
}
@ -991,34 +1085,33 @@ void Config::SavePathValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
WriteGlobalSetting(Settings::values.graphics_api);
WriteGlobalSetting(Settings::values.physical_device);
WriteGlobalSetting(Settings::values.async_command_recording);
WriteGlobalSetting(Settings::values.spirv_shader_gen);
WriteGlobalSetting(Settings::values.use_hw_renderer);
WriteGlobalSetting(Settings::values.use_hw_shader);
#ifdef __APPLE__
// Hardware shader is broken on macos thanks to poor drivers.
// TODO: enable this for none Intel GPUs
WriteSetting(QStringLiteral("use_separable_shader"), Settings::values.separable_shader, false);
WriteGlobalSetting(Settings::values.separable_shader);
#endif
WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul,
true);
WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true);
WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
true);
WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true);
WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1);
WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
WriteSetting(QStringLiteral("use_frame_limit_alternate"),
Settings::values.use_frame_limit_alternate, false);
WriteSetting(QStringLiteral("frame_limit_alternate"), Settings::values.frame_limit_alternate,
200);
WriteGlobalSetting(Settings::values.shaders_accurate_mul);
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
WriteGlobalSetting(Settings::values.use_vsync_new);
WriteGlobalSetting(Settings::values.resolution_factor);
WriteGlobalSetting(Settings::values.frame_limit);
// Cast to double because Qt's written float values are not human-readable
WriteSetting(QStringLiteral("bg_red"), (double)Settings::values.bg_red, 0.0);
WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0);
WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);
WriteSetting(QStringLiteral("texture_filter_name"),
QString::fromStdString(Settings::values.texture_filter_name),
QStringLiteral("none"));
WriteGlobalSetting(Settings::values.texture_filter_name);
if (global) {
WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(),
true);
}
qt_config->endGroup();
}
@ -1045,13 +1138,16 @@ void Config::SaveShortcutValues() {
void Config::SaveSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
WriteSetting(QStringLiteral("is_new_3ds"), Settings::values.is_new_3ds, true);
WriteSetting(QStringLiteral("region_value"), Settings::values.region_value,
Settings::REGION_VALUE_AUTO_SELECT);
WriteSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::values.init_clock),
static_cast<u32>(Settings::InitClock::SystemTime));
WriteSetting(QStringLiteral("init_time"),
static_cast<unsigned long long>(Settings::values.init_time), 946681277ULL);
WriteGlobalSetting(Settings::values.is_new_3ds);
WriteGlobalSetting(Settings::values.region_value);
if (global) {
WriteBasicSetting(Settings::values.init_clock);
WriteBasicSetting(Settings::values.init_time);
WriteBasicSetting(Settings::values.init_time_offset);
WriteBasicSetting(Settings::values.plugin_loader_enabled);
WriteBasicSetting(Settings::values.allow_plugin_loader);
}
qt_config->endGroup();
}
@ -1086,32 +1182,32 @@ void Config::SaveVideoDumpingValues() {
void Config::SaveUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
SavePathValues();
if (global) {
WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
QString::fromUtf8(UISettings::themes[0].second));
WriteSetting(QStringLiteral("enable_discord_presence"),
UISettings::values.enable_discord_presence, true);
WriteSetting(QStringLiteral("screenshot_resolution_factor"),
UISettings::values.screenshot_resolution_factor, 0);
WriteBasicSetting(UISettings::values.enable_discord_presence);
WriteBasicSetting(UISettings::values.screenshot_resolution_factor);
SaveUpdaterValues();
SaveUILayoutValues();
SaveUIGameListValues();
SavePathValues();
SaveShortcutValues();
SaveMultiplayerValues();
WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false);
WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true);
WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true);
WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true);
WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true);
WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
WriteSetting(QStringLiteral("pauseWhenInBackground"),
UISettings::values.pause_when_in_background, false);
WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);
WriteBasicSetting(UISettings::values.single_window_mode);
WriteBasicSetting(UISettings::values.fullscreen);
WriteBasicSetting(UISettings::values.display_titlebar);
WriteBasicSetting(UISettings::values.show_filter_bar);
WriteBasicSetting(UISettings::values.show_status_bar);
WriteBasicSetting(UISettings::values.confirm_before_closing);
WriteBasicSetting(UISettings::values.first_start);
WriteBasicSetting(UISettings::values.callout_flags);
WriteBasicSetting(UISettings::values.show_console);
WriteBasicSetting(UISettings::values.pause_when_in_background);
WriteBasicSetting(UISettings::values.hide_mouse);
}
qt_config->endGroup();
}
@ -1119,13 +1215,11 @@ void Config::SaveUIValues() {
void Config::SaveUIGameListValues() {
qt_config->beginGroup(QStringLiteral("GameList"));
WriteSetting(QStringLiteral("iconSize"),
static_cast<int>(UISettings::values.game_list_icon_size), 2);
WriteSetting(QStringLiteral("row1"), static_cast<int>(UISettings::values.game_list_row_1), 2);
WriteSetting(QStringLiteral("row2"), static_cast<int>(UISettings::values.game_list_row_2), 0);
WriteSetting(QStringLiteral("hideNoIcon"), UISettings::values.game_list_hide_no_icon, false);
WriteSetting(QStringLiteral("singleLineMode"), UISettings::values.game_list_single_line_mode,
false);
WriteBasicSetting(UISettings::values.game_list_icon_size);
WriteBasicSetting(UISettings::values.game_list_row_1);
WriteBasicSetting(UISettings::values.game_list_row_2);
WriteBasicSetting(UISettings::values.game_list_hide_no_icon);
WriteBasicSetting(UISettings::values.game_list_single_line_mode);
qt_config->endGroup();
}
@ -1139,8 +1233,7 @@ void Config::SaveUILayoutValues() {
WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
WriteSetting(QStringLiteral("microProfileDialogGeometry"),
UISettings::values.microprofile_geometry);
WriteSetting(QStringLiteral("microProfileDialogVisible"),
UISettings::values.microprofile_visible, false);
WriteBasicSetting(UISettings::values.microprofile_visible);
qt_config->endGroup();
}
@ -1148,9 +1241,8 @@ void Config::SaveUILayoutValues() {
void Config::SaveUpdaterValues() {
qt_config->beginGroup(QStringLiteral("Updater"));
WriteSetting(QStringLiteral("check_for_update_on_start"),
UISettings::values.check_for_update_on_start, true);
WriteSetting(QStringLiteral("update_on_close"), UISettings::values.update_on_close, false);
WriteBasicSetting(UISettings::values.check_for_update_on_start);
WriteBasicSetting(UISettings::values.update_on_close);
qt_config->endGroup();
}
@ -1184,6 +1276,15 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
template <typename Type>
void Config::ReadSettingGlobal(Type& setting, const QString& name,
const QVariant& default_value) const {
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
if (global || !use_global) {
setting = ReadSetting(name, default_value).value<Type>();
}
}
void Config::WriteSetting(const QString& name, const QVariant& value) {
qt_config->setValue(name, value);
}
@ -1194,11 +1295,21 @@ void Config::WriteSetting(const QString& name, const QVariant& value,
qt_config->setValue(name, value);
}
void Config::WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
bool use_global) {
if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), use_global);
}
if (global || !use_global) {
qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
qt_config->setValue(name, value);
}
}
void Config::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
Settings::Apply();
}
void Config::Save() {

View File

@ -8,13 +8,16 @@
#include <memory>
#include <string>
#include <QVariant>
#include "core/settings.h"
#include "common/settings.h"
class QSettings;
class Config {
public:
Config();
enum class ConfigType : u32 { GlobalConfig, PerGameConfig };
explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
@ -24,6 +27,8 @@ public:
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
private:
void Initialize(const std::string& config_name);
void ReadValues();
void ReadAudioValues();
void ReadCameraValues();
@ -68,11 +73,78 @@ private:
void SaveWebServiceValues();
void SaveVideoDumpingValues();
/**
* Reads a setting from the qt_config.
*
* @param name The setting's identifier
* @param default_value The value to use when the setting is not already present in the config
*/
QVariant ReadSetting(const QString& name) const;
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
/**
* Only reads a setting from the qt_config if the current config is a global config, or if the
* current config is a custom config and the setting is overriding the global setting. Otherwise
* it does nothing.
*
* @param setting The variable to be modified
* @param name The setting's identifier
* @param default_value The value to use when the setting is not already present in the config
*/
template <typename Type>
void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
/**
* Writes a setting to the qt_config.
*
* @param name The setting's idetentifier
* @param value Value of the setting
* @param default_value Default of the setting if not present in qt_config
* @param use_global Specifies if the custom or global config should be in use, for custom
* configs
*/
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
bool use_global);
/**
* Reads a value from the qt_config and applies it to the setting, using its label and default
* value. If the config is a custom config, this will also read the global state of the setting
* and apply that information to it.
*
* @param The setting
*/
template <typename Type, bool ranged>
void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting);
/**
* Sets a value to the qt_config using the setting's label and default value. If the config is a
* custom config, it will apply the global state, and the custom value if needed.
*
* @param The setting
*/
template <typename Type, bool ranged>
void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting);
/**
* Reads a value from the qt_config using the setting's label and default value and applies the
* value to the setting.
*
* @param The setting
*/
template <typename Type, bool ranged>
void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
/** Sets a value from the setting in the qt_config using the setting's label and default value.
*
* @param The setting
*/
template <typename Type, bool ranged>
void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
};

View File

@ -0,0 +1,95 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QCheckBox>
#include <QObject>
#include <QString>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_per_game.h"
#include "common/settings.h"
void ConfigurationShared::ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting,
const QCheckBox* checkbox,
const CheckState& tracker) {
if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
setting->SetValue(checkbox->checkState());
} else if (!Settings::IsConfiguringGlobal()) {
if (tracker == CheckState::Global) {
setting->SetGlobal(true);
} else {
setting->SetGlobal(false);
setting->SetValue(checkbox->checkState());
}
}
}
void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
const Settings::SwitchableSetting<bool>* setting) {
if (setting->UsingGlobal()) {
checkbox->setCheckState(Qt::PartiallyChecked);
} else {
checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
}
}
void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
if (highlighted) {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
.arg(widget->objectName()));
} else {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
.arg(widget->objectName()));
}
widget->show();
}
void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
const Settings::SwitchableSetting<bool>& setting,
CheckState& tracker) {
if (setting.UsingGlobal()) {
tracker = CheckState::Global;
} else {
tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
}
SetHighlight(checkbox, tracker != CheckState::Global);
QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
static_cast<int>(CheckState::Count));
if (tracker == CheckState::Global) {
checkbox->setChecked(setting.GetValue(true));
}
SetHighlight(checkbox, tracker != CheckState::Global);
});
}
void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
bool global_state, CheckState& tracker) {
if (global) {
tracker = CheckState::Global;
} else {
tracker = (state == global_state) ? CheckState::On : CheckState::Off;
}
SetHighlight(checkbox, tracker != CheckState::Global);
QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
static_cast<int>(CheckState::Count));
if (tracker == CheckState::Global) {
checkbox->setChecked(global_state);
}
SetHighlight(checkbox, tracker != CheckState::Global);
});
}
void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
InsertGlobalItem(combobox, global);
QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
[target](int index) { SetHighlight(target, index != 0); });
}
void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
const QString use_global_text =
ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
}

View File

@ -0,0 +1,98 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QCheckBox>
#include <QComboBox>
#include "common/settings.h"
namespace ConfigurationShared {
constexpr int USE_GLOBAL_INDEX =
0; ///< The index of the "Use global configuration" option in checkboxes
constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
constexpr int USE_GLOBAL_OFFSET = 2;
/// CheckBoxes require a tracker for their state since we emulate a tristate CheckBox
enum class CheckState {
Off, ///< Checkbox overrides to off/false
On, ///< Checkbox overrides to on/true
Global, ///< Checkbox defers to the global state
Count, ///< Simply the number of states, not a valid checkbox state
};
/**
* @brief ApplyPerGameSetting given a setting and a Qt UI element, properly applies a Setting
* taking into account the global/per-game check state. This is used for configuring checkboxes
* @param setting
* @param checkbox
* @param tracker
*/
void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox,
const CheckState& tracker);
/**
* @brief ApplyPerGameSetting given a setting and a Qt UI element, properly applies a Setting
* taking into account the global/per-game check state. This is used for both combo boxes
* as well as any other widget that is accompanied by a combo box in per-game settings.
* @param setting The setting class that stores the desired option
* @param combobox The Qt combo box that stores the value/per-game status
* @param transform A function that accepts the combo box index and transforms it to the
* desired settings value. When used with sliders/edit the user can ignore the input value
* and set a custom value this making this function useful for these widgets as well
*/
template <typename Type, bool ranged>
void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
const QComboBox* combobox, auto transform) {
if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
setting->SetValue(static_cast<Type>(transform(combobox->currentIndex())));
} else if (!Settings::IsConfiguringGlobal()) {
if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
setting->SetGlobal(true);
} else {
setting->SetGlobal(false);
setting->SetValue(static_cast<Type>(
transform(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)));
}
}
}
/// Simpler version of ApplyPerGameSetting without a transform parameter
template <typename Type, bool ranged>
void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
const QComboBox* combobox) {
const auto transform = [](s32 index) { return index; };
return ApplyPerGameSetting(setting, combobox, transform);
}
/// Sets a Qt UI element given a Settings::Setting
void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting);
template <typename Type, bool ranged>
void SetPerGameSetting(QComboBox* combobox,
const Settings::SwitchableSetting<Type, ranged>* setting) {
combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
: static_cast<int>(setting->GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET);
}
/// Given a Qt widget sets the background color to indicate whether the setting
/// is per-game overriden (highlighted) or global (non-highlighted)
void SetHighlight(QWidget* widget, bool highlighted);
/// Sets up a QCheckBox like a tristate one, given a Setting
void SetColoredTristate(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>& setting,
CheckState& tracker);
void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
CheckState& tracker);
/// Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls
/// InsertGlobalItem
void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
/// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox
void InsertGlobalItem(QComboBox* combobox, int global_index);
} // namespace ConfigurationShared

View File

@ -9,10 +9,11 @@
#endif
#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_audio.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/frontend/mic.h"
#include "core/settings.h"
#include "ui_configure_audio.h"
#if defined(__APPLE__)
@ -31,25 +32,30 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
}
ui->emulation_combo_box->addItem(tr("HLE (fast)"));
ui->emulation_combo_box->addItem(tr("LLE (accurate)"));
ui->emulation_combo_box->addItem(tr("LLE multi-core"));
ui->emulation_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
const bool is_running = Core::System::GetInstance().IsPoweredOn();
ui->emulation_combo_box->setEnabled(!is_running);
connect(ui->volume_slider, &QSlider::valueChanged, this,
&ConfigureAudio::SetVolumeIndicatorText);
ui->input_device_combo_box->clear();
ui->input_device_combo_box->addItem(tr("Default"));
#ifdef HAVE_CUBEB
for (const auto& device : AudioCore::ListCubebInputDevices()) {
ui->input_device_combo_box->addItem(QString::fromStdString(device));
}
#endif
connect(ui->input_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioInputDevices);
ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
SetupPerGameUI();
SetConfiguration();
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioOutputDevices);
}
@ -61,27 +67,35 @@ void ConfigureAudio::SetConfiguration() {
// The device list cannot be pre-populated (nor listed) until the output sink is known.
UpdateAudioOutputDevices(ui->output_sink_combo_box->currentIndex());
SetAudioDeviceFromDeviceID();
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
ui->volume_slider->setValue(
static_cast<int>(Settings::values.volume * ui->volume_slider->maximum()));
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
const s32 volume =
static_cast<s32>(Settings::values.volume.GetValue() * ui->volume_slider->maximum());
ui->volume_slider->setValue(volume);
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
int selection;
if (Settings::values.enable_dsp_lle) {
if (Settings::values.enable_dsp_lle_multithread) {
selection = 2;
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.volume.UsingGlobal()) {
ui->volume_combo_box->setCurrentIndex(0);
ui->volume_slider->setEnabled(false);
} else {
selection = 1;
ui->volume_combo_box->setCurrentIndex(1);
ui->volume_slider->setEnabled(true);
}
ConfigurationShared::SetHighlight(ui->volume_layout,
!Settings::values.volume.UsingGlobal());
ConfigurationShared::SetHighlight(ui->widget_emulation,
!Settings::values.audio_emulation.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->emulation_combo_box,
&Settings::values.audio_emulation);
} else {
selection = 0;
}
s32 selection = static_cast<s32>(Settings::values.audio_emulation.GetValue());
ui->emulation_combo_box->setCurrentIndex(selection);
}
int index = static_cast<int>(Settings::values.mic_input_type);
s32 index = static_cast<s32>(Settings::values.mic_input_type.GetValue());
ui->input_type_combo_box->setCurrentIndex(index);
UpdateAudioInputDevices(index);
@ -90,7 +104,7 @@ void ConfigureAudio::SetConfiguration() {
void ConfigureAudio::SetOutputSinkFromSinkID() {
int new_sink_index = 0;
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
@ -104,7 +118,7 @@ void ConfigureAudio::SetOutputSinkFromSinkID() {
void ConfigureAudio::SetAudioDeviceFromDeviceID() {
int new_device_index = -1;
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue());
for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
if (ui->audio_device_combo_box->itemText(index) == device_id) {
new_device_index = index;
@ -120,24 +134,31 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
ui->toggle_audio_stretching, audio_stretching);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.audio_emulation,
ui->emulation_combo_box);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.volume, ui->volume_combo_box, [this](s32) {
return static_cast<float>(ui->volume_slider->value()) / ui->volume_slider->maximum();
});
if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
Settings::values.audio_device_id =
ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
.toStdString();
Settings::values.volume =
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0;
Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2;
Settings::values.mic_input_type =
static_cast<Settings::MicInputType>(ui->input_type_combo_box->currentIndex());
if (ui->input_device_combo_box->currentIndex() == DEFAULT_INPUT_DEVICE_INDEX) {
Settings::values.mic_input_device = Frontend::Mic::default_device_name;
} else {
Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString();
Settings::values.mic_input_device =
ui->input_device_combo_box->currentText().toStdString();
}
}
}
@ -157,12 +178,41 @@ void ConfigureAudio::UpdateAudioInputDevices(int index) {
AppleAuthorization::CheckAuthorizationForMicrophone();
}
#endif
if (Settings::values.mic_input_device != Frontend::Mic::default_device_name) {
if (Settings::values.mic_input_device.GetValue() != Frontend::Mic::default_device_name) {
ui->input_device_combo_box->setCurrentText(
QString::fromStdString(Settings::values.mic_input_device));
QString::fromStdString(Settings::values.mic_input_device.GetValue()));
}
}
void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureAudio::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
return;
}
ui->output_sink_combo_box->setVisible(false);
ui->output_sink_label->setVisible(false);
ui->audio_device_combo_box->setVisible(false);
ui->audio_device_label->setVisible(false);
ui->input_type_label->setVisible(false);
ui->input_type_combo_box->setVisible(false);
ui->input_device_label->setVisible(false);
ui->input_device_combo_box->setVisible(false);
ui->microphone_layout->setVisible(false);
connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->volume_slider->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
});
ConfigurationShared::SetColoredComboBox(
ui->emulation_combo_box, ui->widget_emulation,
static_cast<u32>(Settings::values.audio_emulation.GetValue(true)));
ConfigurationShared::SetColoredTristate(
ui->toggle_audio_stretching, Settings::values.enable_audio_stretching, audio_stretching);
}

View File

@ -11,6 +11,10 @@ namespace Ui {
class ConfigureAudio;
}
namespace ConfigurationShared {
enum class CheckState;
}
class ConfigureAudio : public QWidget {
Q_OBJECT
@ -30,5 +34,8 @@ private:
void SetAudioDeviceFromDeviceID();
void SetVolumeIndicatorText(int percentage);
void SetupPerGameUI();
ConfigurationShared::CheckState audio_stretching;
std::unique_ptr<Ui::ConfigureAudio> ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>329</width>
<height>344</height>
<width>696</width>
<height>527</height>
</rect>
</property>
<layout class="QVBoxLayout">
@ -18,7 +18,17 @@
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QWidget" name="widget_emulation" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_emulation">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
@ -30,14 +40,31 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="emulation_combo_box"/>
<widget class="QComboBox" name="emulation_combo_box">
<item>
<property name="text">
<string>HLE (fast)</string>
</property>
</item>
<item>
<property name="text">
<string>LLE (accurate)</string>
</property>
</item>
<item>
<property name="text">
<string>LLE multi-core</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<layout class="QHBoxLayout" name="output_engine_layout">
<item>
<widget class="QLabel" name="label1">
<widget class="QLabel" name="output_sink_label">
<property name="text">
<string>Output Engine</string>
</property>
@ -59,9 +86,9 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<layout class="QHBoxLayout" name="audio_device_layout">
<item>
<widget class="QLabel" name="label2">
<widget class="QLabel" name="audio_device_label">
<property name="text">
<string>Audio Device</string>
</property>
@ -73,12 +100,36 @@
</layout>
</item>
<item>
<widget class="QWidget" name="volume_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<widget class="QComboBox" name="volume_combo_box">
<item>
<property name="text">
<string>Use global volume</string>
</property>
</item>
<item>
<property name="text">
<string>Set volume:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="volume_label">
<property name="text">
<string>Volume:</string>
</property>
@ -91,7 +142,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<width>30</width>
<height>20</height>
</size>
</property>
@ -109,7 +160,7 @@
<number>100</number>
</property>
<property name="pageStep">
<number>10</number>
<number>5</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -133,12 +184,13 @@
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<widget class="QGroupBox" name="microphone_layout">
<property name="title">
<string>Microphone</string>
</property>
@ -146,7 +198,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="input_type_label">
<property name="text">
<string>Input Type</string>
</property>
@ -176,7 +228,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="input_device_label">
<property name="text">
<string>Input Device</string>
</property>

View File

@ -9,12 +9,10 @@
#include <QMessageBox>
#include <QWidget>
#include "citra_qt/configuration/configure_camera.h"
#include "citra_qt/uisettings.h"
#include "core/core.h"
#include "common/settings.h"
#include "core/frontend/camera/factory.h"
#include "core/frontend/camera/interface.h"
#include "core/hle/service/cam/cam.h"
#include "core/settings.h"
#include "ui_configure_camera.h"
#if defined(__APPLE__)
@ -91,7 +89,7 @@ void ConfigureCamera::ConnectEvents() {
SetConfiguration();
});
connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked);
connect(ui->preview_button, &QPushButton::clicked, this, [=] { StartPreviewing(); });
connect(ui->preview_button, &QPushButton::clicked, this, [this] { StartPreviewing(); });
connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) {
ui->camera_file->setDisabled(state == Qt::Checked);
ui->toolButton->setDisabled(state == Qt::Checked);
@ -99,12 +97,11 @@ void ConfigureCamera::ConnectEvents() {
ui->camera_file->setText(QString{});
}
});
connect(ui->camera_file, &QLineEdit::textChanged, this, [=] { StopPreviewing(); });
connect(ui->system_camera,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[=] { StopPreviewing(); });
connect(ui->camera_flip, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, [=] { StopPreviewing(); });
connect(ui->camera_file, &QLineEdit::textChanged, this, [this] { StopPreviewing(); });
connect(ui->system_camera, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this] { StopPreviewing(); });
connect(ui->camera_flip, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this] { StopPreviewing(); });
}
void ConfigureCamera::UpdateCameraMode() {

View File

@ -3,17 +3,18 @@
// Refer to the license.txt file included.
#include <QDesktopServices>
#include <QMessageBox>
#include <QUrl>
#include "citra_qt/configuration/configure_debug.h"
#include "citra_qt/debugger/console.h"
#include "citra_qt/uisettings.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/settings.h"
#include "qcheckbox.h"
#include "ui_configure_debug.h"
#include "video_core/renderer_vulkan/vk_instance.h"
ConfigureDebug::ConfigureDebug(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureDebug>()) {
@ -24,19 +25,55 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) {
if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) {
try {
Vulkan::Instance debug_inst{true};
} catch (vk::LayerNotPresentError&) {
ui->toggle_renderer_debug->toggle();
QMessageBox::warning(this, tr("Validation layer not available"),
tr("Unable to enable debug renderer because the layer "
"<strong>VK_LAYER_KHRONOS_validation</strong> is missing. "
"Please install the Vulkan SDK or the appropriate package "
"of your distribution"));
}
}
});
connect(ui->toggle_dump_command_buffers, &QCheckBox::clicked, this, [this](bool checked) {
if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) {
try {
Vulkan::Instance debug_inst{false, true};
} catch (vk::LayerNotPresentError&) {
ui->toggle_dump_command_buffers->toggle();
QMessageBox::warning(this, tr("Command buffer dumping not available"),
tr("Unable to enable command buffer dumping because the layer "
"<strong>VK_LAYER_LUNARG_api_dump</strong> is missing. "
"Please install the Vulkan SDK or the appropriate package "
"of your distribution"));
}
}
});
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
ui->toggle_dump_command_buffers->setEnabled(!is_powered_on);
}
ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue());
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
}
void ConfigureDebug::ApplyConfiguration() {
@ -46,9 +83,11 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
filter.ParseFilterString(Settings::values.log_filter.GetValue());
Log::SetGlobalFilter(filter);
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
}
void ConfigureDebug::RetranslateUI() {

View File

@ -22,5 +22,6 @@ public:
void RetranslateUI();
void SetConfiguration();
private:
std::unique_ptr<Ui::ConfigureDebug> ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>443</width>
<height>300</height>
<width>454</width>
<height>356</height>
</rect>
</property>
<property name="windowTitle">
@ -114,11 +114,31 @@
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="toggle_cpu_jit">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable CPU JIT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_renderer_debug">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables debug reporting in the currently selected graphics API. Causes measurable performance loss, don't enable unless for debugging purposes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable debug renderer</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text">
<string>Dump command buffers</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -7,11 +7,13 @@
#include "citra_qt/configuration/config.h"
#include "citra_qt/configuration/configure_dialog.h"
#include "citra_qt/hotkeys.h"
#include "core/settings.h"
#include "common/settings.h"
#include "ui_configure.h"
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, bool enable_web_config)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureDialog>()), registry(registry) {
Settings::SetConfiguringGlobal(true);
ui->setupUi(this);
ui->hotkeysTab->Populate(registry);
ui->webTab->SetWebServiceConfigEnabled(enable_web_config);

View File

@ -4,8 +4,8 @@
#include <QColorDialog>
#include "citra_qt/configuration/configure_enhancements.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_enhancements.h"
#include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
@ -21,7 +21,7 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
ui->layoutBox->setEnabled(!Settings::values.custom_layout);
ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer);
ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer.GetValue());
connect(ui->render_3d_combobox,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@ -50,26 +50,31 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
}
void ConfigureEnhancements::SetConfiguration() {
ui->resolution_factor_combobox->setCurrentIndex(Settings::values.resolution_factor);
ui->render_3d_combobox->setCurrentIndex(static_cast<int>(Settings::values.render_3d));
ui->factor_3d->setValue(Settings::values.factor_3d);
updateShaders(Settings::values.render_3d);
ui->toggle_linear_filter->setChecked(Settings::values.filter_mode);
ui->resolution_factor_combobox->setCurrentIndex(Settings::values.resolution_factor.GetValue());
ui->render_3d_combobox->setCurrentIndex(
static_cast<int>(Settings::values.render_3d.GetValue()));
ui->factor_3d->setValue(Settings::values.factor_3d.GetValue());
ui->mono_rendering_eye->setCurrentIndex(
static_cast<int>(Settings::values.mono_render_option.GetValue()));
updateShaders(Settings::values.render_3d.GetValue());
ui->toggle_linear_filter->setChecked(Settings::values.filter_mode.GetValue());
int tex_filter_idx = ui->texture_filter_combobox->findText(
QString::fromStdString(Settings::values.texture_filter_name));
QString::fromStdString(Settings::values.texture_filter_name.GetValue()));
if (tex_filter_idx == -1) {
ui->texture_filter_combobox->setCurrentIndex(0);
} else {
ui->texture_filter_combobox->setCurrentIndex(tex_filter_idx);
}
ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
ui->swap_screen->setChecked(Settings::values.swap_screen);
ui->upright_screen->setChecked(Settings::values.upright_screen);
ui->toggle_dump_textures->setChecked(Settings::values.dump_textures);
ui->toggle_custom_textures->setChecked(Settings::values.custom_textures);
ui->toggle_preload_textures->setChecked(Settings::values.preload_textures);
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue);
ui->layout_combobox->setCurrentIndex(
static_cast<int>(Settings::values.layout_option.GetValue()));
ui->swap_screen->setChecked(Settings::values.swap_screen.GetValue());
ui->upright_screen->setChecked(Settings::values.upright_screen.GetValue());
ui->toggle_dump_textures->setChecked(Settings::values.dump_textures.GetValue());
ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue());
ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue());
bg_color =
QColor::fromRgbF(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
Settings::values.bg_blue.GetValue());
QPixmap pixmap(ui->bg_button->size());
pixmap.fill(bg_color);
const QIcon color_icon(pixmap);
@ -92,7 +97,7 @@ void ConfigureEnhancements::updateShaders(Settings::StereoRenderOption stereo_op
for (const auto& shader : OpenGL::GetPostProcessingShaderList(
stereo_option == Settings::StereoRenderOption::Anaglyph)) {
ui->shader_combobox->addItem(QString::fromStdString(shader));
if (Settings::values.pp_shader_name == shader)
if (Settings::values.pp_shader_name.GetValue() == shader)
ui->shader_combobox->setCurrentIndex(ui->shader_combobox->count() - 1);
}
}
@ -107,6 +112,8 @@ void ConfigureEnhancements::ApplyConfiguration() {
Settings::values.render_3d =
static_cast<Settings::StereoRenderOption>(ui->render_3d_combobox->currentIndex());
Settings::values.factor_3d = ui->factor_3d->value();
Settings::values.mono_render_option =
static_cast<Settings::MonoRenderOption>(ui->mono_rendering_eye->currentIndex());
Settings::values.pp_shader_name =
ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString();
Settings::values.filter_mode = ui->toggle_linear_filter->isChecked();

View File

@ -6,9 +6,10 @@
#include <memory>
#include <QWidget>
#include "common/common_types.h"
namespace Settings {
enum class StereoRenderOption;
enum class StereoRenderOption : u32;
}
namespace Ui {

View File

@ -207,6 +207,31 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Eye to Render in Monoscopic Mode</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mono_rendering_eye">
<item>
<property name="text">
<string>Left Eye (default)</string>
</property>
</item>
<item>
<property name="text">
<string>Right Eye</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -247,6 +272,11 @@
<string>Side by Side</string>
</property>
</item>
<item>
<property name="text">
<string>Separate Windows</string>
</property>
</item>
</widget>
</item>
</layout>
@ -350,6 +380,7 @@
<tabstop>texture_filter_combobox</tabstop>
<tabstop>render_3d_combobox</tabstop>
<tabstop>factor_3d</tabstop>
<tabstop>mono_rendering_eye</tabstop>
<tabstop>layout_combobox</tabstop>
<tabstop>swap_screen</tabstop>
<tabstop>upright_screen</tabstop>

View File

@ -6,10 +6,11 @@
#include <QFileDialog>
#include <QMessageBox>
#include <QUrl>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_general.h"
#include "citra_qt/uisettings.h"
#include "core/core.h"
#include "core/settings.h"
#include "common/file_util.h"
#include "common/settings.h"
#include "ui_configure_general.h"
// The QSlider doesn't have an easy way to set a custom step amount,
@ -31,14 +32,17 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
// Set a minimum width for the label to prevent the slider from changing size.
// This scales across DPIs, and is acceptable for uncapitalized strings.
ui->emulation_speed_display_label->setMinimumWidth(tr("unthrottled").size() * 6);
ui->emulation_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
ui->screenshot_combo->setVisible(!Settings::IsConfiguringGlobal());
SetupPerGameUI();
SetConfiguration();
ui->updateBox->setVisible(UISettings::values.updater_found);
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
&ConfigureGeneral::ResetDefaults);
connect(ui->frame_limit, &QSlider::valueChanged, [&](int value) {
connect(ui->frame_limit, &QSlider::valueChanged, this, [&](int value) {
if (value == ui->frame_limit->maximum()) {
ui->emulation_speed_display_label->setText(tr("unthrottled"));
} else {
@ -49,17 +53,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
}
});
connect(ui->frame_limit_alternate, &QSlider::valueChanged, [&](int value) {
if (value == ui->frame_limit_alternate->maximum()) {
ui->emulation_speed_alternate_display_label->setText(tr("unthrottled"));
} else {
ui->emulation_speed_alternate_display_label->setText(
QStringLiteral("%1%")
.arg(SliderToSettings(value))
.rightJustified(tr("unthrottled").size()));
}
});
connect(ui->change_screenshot_dir, &QToolButton::clicked, this, [this] {
const QString dir_path = QFileDialog::getExistingDirectory(
this, tr("Select Screenshot Directory"), ui->screenshot_dir_path->text(),
@ -73,20 +66,21 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse);
if (Settings::IsConfiguringGlobal()) {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
ui->toggle_background_pause->setChecked(
UISettings::values.pause_when_in_background.GetValue());
ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
ui->toggle_update_check->setChecked(UISettings::values.check_for_update_on_start);
ui->toggle_auto_update->setChecked(UISettings::values.update_on_close);
ui->toggle_update_check->setChecked(
UISettings::values.check_for_update_on_start.GetValue());
ui->toggle_auto_update->setChecked(UISettings::values.update_on_close.GetValue());
}
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
if (Settings::values.frame_limit == 0) {
if (Settings::values.frame_limit.GetValue() == 0) {
ui->frame_limit->setValue(ui->frame_limit->maximum());
} else {
ui->frame_limit->setValue(SettingsToSlider(Settings::values.frame_limit));
ui->frame_limit->setValue(SettingsToSlider(Settings::values.frame_limit.GetValue()));
}
if (ui->frame_limit->value() == ui->frame_limit->maximum()) {
ui->emulation_speed_display_label->setText(tr("unthrottled"));
@ -97,32 +91,48 @@ void ConfigureGeneral::SetConfiguration() {
.rightJustified(tr("unthrottled").size()));
}
ui->toggle_alternate_speed->setChecked(Settings::values.use_frame_limit_alternate);
if (Settings::values.frame_limit_alternate == 0) {
ui->frame_limit_alternate->setValue(ui->frame_limit_alternate->maximum());
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.frame_limit.UsingGlobal()) {
ui->emulation_speed_combo->setCurrentIndex(0);
ui->frame_limit->setEnabled(false);
} else {
ui->frame_limit_alternate->setValue(
SettingsToSlider(Settings::values.frame_limit_alternate));
ui->emulation_speed_combo->setCurrentIndex(1);
ui->frame_limit->setEnabled(true);
}
if (ui->frame_limit_alternate->value() == ui->frame_limit_alternate->maximum()) {
ui->emulation_speed_alternate_display_label->setText(tr("unthrottled"));
if (UISettings::values.screenshot_path.UsingGlobal()) {
ui->screenshot_combo->setCurrentIndex(0);
ui->screenshot_dir_path->setEnabled(false);
ui->change_screenshot_dir->setEnabled(false);
} else {
ui->emulation_speed_alternate_display_label->setText(
QStringLiteral("%1%")
.arg(SliderToSettings(ui->frame_limit_alternate->value()))
.rightJustified(tr("unthrottled").size()));
ui->screenshot_combo->setCurrentIndex(1);
ui->screenshot_dir_path->setEnabled(true);
ui->change_screenshot_dir->setEnabled(true);
}
ConfigurationShared::SetHighlight(ui->widget_screenshot,
!UISettings::values.screenshot_path.UsingGlobal());
ConfigurationShared::SetHighlight(ui->emulation_speed_layout,
!Settings::values.frame_limit.UsingGlobal());
ConfigurationShared::SetHighlight(ui->widget_region,
!Settings::values.region_value.UsingGlobal());
const bool is_region_global = Settings::values.region_value.UsingGlobal();
ui->region_combobox->setCurrentIndex(
is_region_global ? ConfigurationShared::USE_GLOBAL_INDEX
: static_cast<int>(Settings::values.region_value.GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET + 1);
} else {
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
ui->region_combobox->setCurrentIndex(Settings::values.region_value.GetValue() + 1);
}
QString screenshot_path = UISettings::values.screenshot_path;
if (screenshot_path.isEmpty()) {
screenshot_path =
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
screenshot_path.append(QStringLiteral("screenshots/"));
FileUtil::CreateFullPath(screenshot_path.toStdString());
UISettings::values.screenshot_path.SetGlobal(ui->screenshot_combo->currentIndex() ==
ConfigurationShared::USE_GLOBAL_INDEX);
std::string screenshot_path = UISettings::values.screenshot_path.GetValue();
if (screenshot_path.empty()) {
screenshot_path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir) + "screenshots/";
FileUtil::CreateFullPath(screenshot_path);
UISettings::values.screenshot_path = screenshot_path;
}
ui->screenshot_dir_path->setText(screenshot_path);
ui->screenshot_dir_path->setText(QString::fromStdString(screenshot_path));
}
void ConfigureGeneral::ResetDefaults() {
@ -131,39 +141,67 @@ void ConfigureGeneral::ResetDefaults() {
tr("Are you sure you want to <b>reset your settings</b> and close Citra?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer == QMessageBox::No)
if (answer == QMessageBox::No) {
return;
}
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini");
FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "custom");
std::exit(0);
}
void ConfigureGeneral::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_value, ui->region_combobox,
[](s32 index) { return index - 1; });
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.frame_limit, ui->emulation_speed_combo, [this](s32) {
const bool is_maximum = ui->frame_limit->value() == ui->frame_limit->maximum();
return is_maximum ? 0 : SliderToSettings(ui->frame_limit->value());
});
ConfigurationShared::ApplyPerGameSetting(
&UISettings::values.screenshot_path, ui->screenshot_combo,
[this](s32) { return ui->screenshot_dir_path->text().toStdString(); });
if (Settings::IsConfiguringGlobal()) {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
UISettings::values.screenshot_path = ui->screenshot_dir_path->text();
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
if (ui->frame_limit->value() == ui->frame_limit->maximum()) {
Settings::values.frame_limit = 0;
} else {
Settings::values.frame_limit = SliderToSettings(ui->frame_limit->value());
}
Settings::values.use_frame_limit_alternate = ui->toggle_alternate_speed->isChecked();
if (ui->frame_limit_alternate->value() == ui->frame_limit_alternate->maximum()) {
Settings::values.frame_limit_alternate = 0;
} else {
Settings::values.frame_limit_alternate =
SliderToSettings(ui->frame_limit_alternate->value());
}
}
void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGeneral::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->region_combobox->setEnabled(Settings::values.region_value.UsingGlobal());
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
return;
}
connect(ui->emulation_speed_combo, qOverload<int>(&QComboBox::activated), this,
[this](int index) {
ui->frame_limit->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->emulation_speed_layout, index == 1);
});
connect(ui->screenshot_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->screenshot_dir_path->setEnabled(index == 1);
ui->change_screenshot_dir->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->widget_screenshot, index == 1);
});
ui->general_group->setVisible(false);
ui->updateBox->setVisible(false);
ui->button_reset_defaults->setVisible(false);
ConfigurationShared::SetColoredComboBox(
ui->region_combobox, ui->widget_region,
static_cast<u32>(Settings::values.region_value.GetValue(true) + 1));
}

View File

@ -25,6 +25,8 @@ public:
void RetranslateUI();
void SetConfiguration();
void SetupPerGameUI();
private:
std::unique_ptr<Ui::ConfigureGeneral> ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>408</width>
<height>436</height>
<width>524</width>
<height>578</height>
</rect>
</property>
<property name="windowTitle">
@ -17,7 +17,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="general_group">
<property name="title">
<string>General</string>
</property>
@ -70,12 +70,34 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<widget class="QGroupBox" name="emulation_group">
<property name="title">
<string>Emulation</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget_region" native="true">
<layout class="QHBoxLayout" name="region_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="region_label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="region_combobox">
<item>
<property name="text">
@ -119,21 +141,46 @@
</item>
</widget>
</item>
<item row="1" column="0">
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="emulation_speed_layout" native="true">
<layout class="QHBoxLayout" name="emulation_speed_layout_inner">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="emulation_speed_combo">
<item>
<property name="text">
<string>Use global emulation speed</string>
</property>
</item>
<item>
<property name="text">
<string>Set emulation speed:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_emulation_speed">
<property name="text">
<string>Emulation Speed:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="toggle_alternate_speed">
<property name="text">
<string>Use Alternate Speed:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<item>
<widget class="QSlider" name="frame_limit">
<property name="minimum">
<number>0</number>
@ -158,7 +205,7 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item>
<widget class="QLabel" name="emulation_speed_display_label">
<property name="text">
<string/>
@ -168,48 +215,9 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSlider" name="frame_limit_alternate">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>199</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>15</number>
</property>
<property name="value">
<number>39</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="emulation_speed_alternate_display_label">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
@ -218,17 +226,48 @@
<property name="title">
<string>Screenshots</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QGridLayout" name="gridLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QWidget" name="widget_screenshot" native="true">
<layout class="QHBoxLayout" name="screenshot_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<widget class="QComboBox" name="screenshot_combo">
<item>
<property name="text">
<string>Use global screenshot path</string>
</property>
</item>
<item>
<property name="text">
<string>Set screenshot path:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="screenshot_dir_label">
<property name="text">
<string>Save Screenshots To</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="screenshot_dir_path">
</widget>
<widget class="QLineEdit" name="screenshot_dir_path"/>
</item>
<item>
<widget class="QToolButton" name="change_screenshot_dir">
@ -240,6 +279,9 @@
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="button_reset_defaults">
<property name="text">
@ -270,12 +312,6 @@
<tabstop>toggle_hide_mouse</tabstop>
<tabstop>toggle_update_check</tabstop>
<tabstop>toggle_auto_update</tabstop>
<tabstop>region_combobox</tabstop>
<tabstop>frame_limit</tabstop>
<tabstop>toggle_alternate_speed</tabstop>
<tabstop>frame_limit_alternate</tabstop>
<tabstop>screenshot_dir_path</tabstop>
<tabstop>change_screenshot_dir</tabstop>
<tabstop>button_reset_defaults</tabstop>
</tabstops>
<resources/>

View File

@ -6,22 +6,37 @@
#ifdef __APPLE__
#include <QMessageBox>
#endif
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_graphics.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
#include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_vulkan/vk_instance.h"
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
ui->setupUi(this);
DiscoverPhysicalDevices();
SetupPerGameUI();
SetConfiguration();
ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked());
ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
const bool not_running = !Core::System::GetInstance().IsPoweredOn();
const bool hw_renderer_enabled = ui->toggle_hw_renderer->isChecked();
ui->toggle_hw_renderer->setEnabled(not_running);
ui->hw_renderer_group->setEnabled(hw_renderer_enabled && not_running);
ui->toggle_vsync_new->setEnabled(not_running);
ui->graphics_api_combo->setEnabled(not_running);
ui->toggle_shader_jit->setEnabled(not_running);
ui->toggle_disk_shader_cache->setEnabled(hw_renderer_enabled && not_running);
ui->physical_device_combo->setEnabled(not_running);
SetPhysicalDeviceComboVisibility(ui->graphics_api_combo->currentIndex());
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
auto checked = ui->toggle_hw_renderer->isChecked();
const bool checked = ui->toggle_hw_renderer->isChecked();
ui->hw_renderer_group->setEnabled(checked);
ui->toggle_disk_shader_cache->setEnabled(checked && ui->toggle_hw_shader->isChecked());
});
@ -31,7 +46,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
ui->toggle_hw_shader->isChecked());
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
auto checked = ui->toggle_hw_shader->isChecked();
const bool checked = ui->toggle_hw_shader->isChecked();
ui->hw_shader_group->setEnabled(checked);
ui->toggle_disk_shader_cache->setEnabled(checked);
});
@ -62,25 +77,100 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
ConfigureGraphics::~ConfigureGraphics() = default;
void ConfigureGraphics::SetConfiguration() {
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader);
ui->toggle_separable_shader->setChecked(Settings::values.separable_shader);
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul);
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new);
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer.GetValue());
ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader.GetValue());
ui->toggle_separable_shader->setChecked(Settings::values.separable_shader.GetValue());
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new.GetValue());
ui->graphics_api_combo->setCurrentIndex(
static_cast<int>(Settings::values.graphics_api.GetValue()));
ui->physical_device_combo->setCurrentIndex(
static_cast<int>(Settings::values.physical_device.GetValue()));
ui->toggle_async_recording->setChecked(Settings::values.async_command_recording.GetValue());
ui->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
}
}
void ConfigureGraphics::ApplyConfiguration() {
Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked();
Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked();
Settings::values.separable_shader = ui->toggle_separable_shader->isChecked();
Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked();
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_renderer,
ui->toggle_hw_renderer, use_hw_renderer);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_shader, ui->toggle_hw_shader,
use_hw_shader);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.separable_shader,
ui->toggle_separable_shader, separable_shader);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.shaders_accurate_mul,
ui->toggle_accurate_mul, shaders_accurate_mul);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
ui->toggle_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new,
use_vsync_new);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.graphics_api,
ui->graphics_api_combo);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.physical_device,
ui->physical_device_combo);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_command_recording,
ui->toggle_async_recording, async_command_recording);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.spirv_shader_gen,
ui->spirv_shader_gen, spirv_shader_gen);
if (Settings::IsConfiguringGlobal()) {
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked();
}
}
void ConfigureGraphics::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGraphics::SetupPerGameUI() {
// Block the global settings if a game is currently running that overrides them
if (Settings::IsConfiguringGlobal()) {
ui->toggle_hw_renderer->setEnabled(Settings::values.use_hw_renderer.UsingGlobal());
ui->toggle_hw_shader->setEnabled(Settings::values.use_hw_shader.UsingGlobal());
ui->toggle_separable_shader->setEnabled(Settings::values.separable_shader.UsingGlobal());
ui->toggle_accurate_mul->setEnabled(Settings::values.shaders_accurate_mul.UsingGlobal());
ui->toggle_disk_shader_cache->setEnabled(
Settings::values.use_disk_shader_cache.UsingGlobal());
ui->toggle_vsync_new->setEnabled(Settings::values.use_vsync_new.UsingGlobal());
return;
}
ui->toggle_shader_jit->setVisible(false);
ConfigurationShared::SetColoredTristate(ui->toggle_hw_renderer,
Settings::values.use_hw_renderer, use_hw_renderer);
ConfigurationShared::SetColoredTristate(ui->toggle_hw_shader, Settings::values.use_hw_shader,
use_hw_shader);
ConfigurationShared::SetColoredTristate(ui->toggle_separable_shader,
Settings::values.separable_shader, separable_shader);
ConfigurationShared::SetColoredTristate(
ui->toggle_accurate_mul, Settings::values.shaders_accurate_mul, shaders_accurate_mul);
ConfigurationShared::SetColoredTristate(ui->toggle_disk_shader_cache,
Settings::values.use_disk_shader_cache,
use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new,
use_vsync_new);
}
void ConfigureGraphics::DiscoverPhysicalDevices() {
Vulkan::Instance instance{};
const auto physical_devices = instance.GetPhysicalDevices();
ui->physical_device_combo->clear();
for (const vk::PhysicalDevice& physical_device : physical_devices) {
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
ui->physical_device_combo->addItem(name);
}
}
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
const bool is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
ui->physical_device_label->setVisible(is_visible);
ui->physical_device_combo->setVisible(is_visible);
ui->spirv_shader_gen->setVisible(is_visible);
}

View File

@ -11,6 +11,10 @@ namespace Ui {
class ConfigureGraphics;
}
namespace ConfigurationShared {
enum class CheckState;
}
class ConfigureGraphics : public QWidget {
Q_OBJECT
@ -24,6 +28,20 @@ public:
void UpdateBackgroundColorButton(const QColor& color);
void SetupPerGameUI();
private:
void DiscoverPhysicalDevices();
void SetPhysicalDeviceComboVisibility(int index);
ConfigurationShared::CheckState use_hw_renderer;
ConfigurationShared::CheckState use_hw_shader;
ConfigurationShared::CheckState separable_shader;
ConfigurationShared::CheckState shaders_accurate_mul;
ConfigurationShared::CheckState use_disk_shader_cache;
ConfigurationShared::CheckState use_vsync_new;
ConfigurationShared::CheckState async_command_recording;
ConfigurationShared::CheckState spirv_shader_gen;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
};

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>430</height>
<height>513</height>
</rect>
</property>
<property name="minimumSize">
@ -20,6 +20,66 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="apiBox">
<property name="title">
<string>API Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="graphics_api_label">
<property name="text">
<string>Graphics API</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="graphics_api_combo">
<item>
<property name="text">
<string>OpenGL</string>
</property>
</item>
<item>
<property name="text">
<string>OpenGLES</string>
</property>
</item>
<item>
<property name="text">
<string>Vulkan</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="physical_device_label">
<property name="text">
<string>Physical device</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="physical_device_combo"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="spirv_shader_gen">
<property name="text">
<string>SPIR-V Shader Generation</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="rendererBox">
<property name="title">
@ -118,6 +178,16 @@
<string>Advanced</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_async_recording">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Offloads command buffer recording and fragment shader generation to a worker thread. Can improve performance especially on weaker systems. Disable if you notice better performance. If unsure leave it enabled,&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Async Command Recording</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_disk_shader_cache">
<property name="toolTip">

View File

@ -7,7 +7,7 @@
#include "citra_qt/configuration/configure_hotkeys.h"
#include "citra_qt/hotkeys.h"
#include "citra_qt/util/sequence_dialog/sequence_dialog.h"
#include "core/settings.h"
#include "common/settings.h"
#include "ui_configure_hotkeys.h"
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)

View File

@ -192,11 +192,24 @@ ConfigureInput::ConfigureInput(QWidget* parent)
if (!button_map[button_id])
continue;
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button_map[button_id], &QPushButton::clicked, [=]() {
connect(button_map[button_id], &QPushButton::clicked, [this, button_id]() {
HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) {
buttons_param[button_id] = params;
[this, button_id](Common::ParamPackage params) {
// Workaround for ZL & ZR for analog triggers like on XBOX controllors.
// Analog triggers (from controllers like the XBOX controller) would not
// work due to a different range of their signals (from 0 to 255 on
// analog triggers instead of -32768 to 32768 on analog joysticks). The
// SDL driver misinterprets analog triggers as analog joysticks.
// TODO: reinterpret the signal range for analog triggers to map the
// values correctly. This is required for the correct emulation of the
// analog triggers of the GameCube controller.
if (button_id == Settings::NativeButton::ZL ||
button_id == Settings::NativeButton::ZR) {
params.Set("direction", "+");
params.Set("threshold", "0.5");
}
buttons_param[button_id] = std::move(params);
// If the user closes the dialog, the changes are reverted in
// `GMainWindow::OnConfigure()`
ApplyConfiguration();
@ -204,16 +217,16 @@ ConfigureInput::ConfigureInput(QWidget* parent)
},
InputCommon::Polling::DeviceType::Button);
});
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
connect(button_map[button_id], &QPushButton::customContextMenuRequested, this,
[this, button_id](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
@ -230,10 +243,11 @@ ConfigureInput::ConfigureInput(QWidget* parent)
continue;
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
Qt::CustomContextMenu);
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, [=]() {
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, this,
[this, analog_id, sub_button_id]() {
HandleClick(
analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
[this, analog_id, sub_button_id](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
ApplyConfiguration();
@ -242,15 +256,16 @@ ConfigureInput::ConfigureInput(QWidget* parent)
InputCommon::Polling::DeviceType::Button);
});
connect(analog_map_buttons[analog_id][sub_button_id],
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
&QPushButton::customContextMenuRequested, this,
[this, analog_id, sub_button_id](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id],
@ -264,7 +279,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
menu_location));
});
}
connect(analog_map_stick[analog_id], &QPushButton::clicked, [=]() {
connect(analog_map_stick[analog_id], &QPushButton::clicked, this, [this, analog_id]() {
if (QMessageBox::information(
this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
@ -272,7 +287,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
HandleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) {
[this, analog_id](const Common::ParamPackage& params) {
analogs_param[analog_id] = params;
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
@ -280,8 +295,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
InputCommon::Polling::DeviceType::Analog);
}
});
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, this,
[this, analog_id] {
const int slider_value =
analog_map_deadzone_and_modifier_slider[analog_id]->value();
const auto engine = analogs_param[analog_id].Get("engine", "");
if (engine == "sdl" || engine == "gcpad") {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
@ -299,10 +316,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
// The Circle Mod button is common for both the sticks, so update the modifier settings
// for both the sticks.
connect(ui->buttonCircleMod, &QPushButton::clicked, [=]() {
connect(ui->buttonCircleMod, &QPushButton::clicked, this, [this]() {
HandleClick(
ui->buttonCircleMod,
[=](const Common::ParamPackage& params) {
[this](const Common::ParamPackage& params) {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
SetAnalogButton(params, analogs_param[analog_id], "modifier");
@ -312,10 +329,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
},
InputCommon::Polling::DeviceType::Button);
});
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested,
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested, this,
[&](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
analogs_param[analog_id].Erase("modifier");
@ -325,7 +342,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
@ -341,7 +358,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location));
});
connect(ui->buttonMotionTouch, &QPushButton::clicked, [this] {
connect(ui->buttonMotionTouch, &QPushButton::clicked, this, [this] {
QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
return motion_touch_dialog->exec();
});
@ -356,8 +373,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile);
connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile);
connect(ui->profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this](int i) {
connect(ui->profile, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int i) {
ApplyConfiguration();
Settings::SaveProfile(Settings::values.current_input_profile_index);
Settings::LoadProfile(i);
@ -365,9 +381,9 @@ ConfigureInput::ConfigureInput(QWidget* parent)
});
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
@ -554,7 +570,7 @@ void ConfigureInput::AutoMap() {
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
return;
}
input_setter = [=](const Common::ParamPackage& params) {
input_setter = [this](const Common::ParamPackage& params) {
MapFromButton(params);
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());

View File

@ -13,7 +13,7 @@
#include <QKeySequence>
#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
#include "common/settings.h"
#include "input_common/main.h"
class QKeyEvent;

View File

@ -23,8 +23,9 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
status_label = new QLabel(tr("Communicating with the server..."));
cancel_button = new QPushButton(tr("Cancel"));
connect(cancel_button, &QPushButton::clicked, this, [this] {
if (!completed)
if (!completed) {
job->Stop();
}
accept();
});
layout->addWidget(status_label);
@ -46,6 +47,9 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
case CalibrationConfigurationJob::Status::Completed:
text = tr("Configuration completed!");
break;
default:
LOG_ERROR(Frontend, "Unknown calibration status {}", status);
break;
}
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
if (status == CalibrationConfigurationJob::Status::Completed) {
@ -63,31 +67,33 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
void CalibrationConfigurationDialog::UpdateLabelText(QString text) {
void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
status_label->setText(text);
}
void CalibrationConfigurationDialog::UpdateButtonText(QString text) {
void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
cancel_button->setText(text);
}
const std::array<std::pair<const char*, const char*>, 3> MotionProviders = {
{{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
constexpr std::array<std::pair<const char*, const char*>, 3> MotionProviders = {{
{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
{"sdl", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "SDL")}}};
{"sdl", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "SDL")},
}};
const std::array<std::pair<const char*, const char*>, 2> TouchProviders = {
{{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}};
constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
}};
ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
for (auto [provider, name] : MotionProviders) {
for (const auto& [provider, name] : MotionProviders) {
ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
}
for (auto [provider, name] : TouchProviders) {
for (const auto& [provider, name] : TouchProviders) {
ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
}
@ -99,9 +105,9 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
@ -122,10 +128,10 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
ConfigureMotionTouch::~ConfigureMotionTouch() = default;
void ConfigureMotionTouch::SetConfiguration() {
Common::ParamPackage motion_param(Settings::values.current_input_profile.motion_device);
Common::ParamPackage touch_param(Settings::values.current_input_profile.touch_device);
std::string motion_engine = motion_param.Get("engine", "motion_emu");
std::string touch_engine = touch_param.Get("engine", "emu_window");
const Common::ParamPackage motion_param(Settings::values.current_input_profile.motion_device);
const Common::ParamPackage touch_param(Settings::values.current_input_profile.touch_device);
const std::string motion_engine = motion_param.Get("engine", "motion_emu");
const std::string touch_engine = touch_param.Get("engine", "emu_window");
ui->motion_provider->setCurrentIndex(
ui->motion_provider->findData(QString::fromStdString(motion_engine)));
@ -156,8 +162,8 @@ void ConfigureMotionTouch::SetConfiguration() {
}
void ConfigureMotionTouch::UpdateUiDisplay() {
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
const std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
const std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
if (motion_engine == "motion_emu") {
ui->motion_sensitivity_label->setVisible(true);
@ -179,9 +185,8 @@ void ConfigureMotionTouch::UpdateUiDisplay() {
ui->touch_calibration->setVisible(true);
ui->touch_calibration_config->setVisible(true);
ui->touch_calibration_label->setVisible(true);
ui->touch_calibration->setText(QStringLiteral("(%1, %2) - (%3, %4)")
.arg(QString::number(min_x), QString::number(min_y),
QString::number(max_x), QString::number(max_y)));
ui->touch_calibration->setText(
QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
} else {
ui->touch_calibration->setVisible(false);
ui->touch_calibration_config->setVisible(false);
@ -196,13 +201,11 @@ void ConfigureMotionTouch::UpdateUiDisplay() {
}
void ConfigureMotionTouch::ConnectEvents() {
connect(ui->motion_provider,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this]([[maybe_unused]] int index) { UpdateUiDisplay(); });
connect(ui->touch_provider,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this]([[maybe_unused]] int index) { UpdateUiDisplay(); });
connect(ui->motion_controller_button, &QPushButton::clicked, [=]() {
connect(ui->motion_controller_button, &QPushButton::clicked, this, [this]() {
if (QMessageBox::information(this, tr("Information"),
tr("After pressing OK, press a button on the controller whose "
"motion you want to track."),
@ -210,7 +213,7 @@ void ConfigureMotionTouch::ConnectEvents() {
ui->motion_controller_button->setText(tr("[press button]"));
ui->motion_controller_button->setFocus();
input_setter = [=](const Common::ParamPackage& params) {
input_setter = [this](const Common::ParamPackage& params) {
guid = params.Get("guid", "0");
port = params.Get("port", 0);
};
@ -232,8 +235,9 @@ void ConfigureMotionTouch::ConnectEvents() {
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchFromButton);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
if (CanCloseDialog())
if (CanCloseDialog()) {
reject();
}
});
}
@ -272,15 +276,15 @@ void ConfigureMotionTouch::OnCemuhookUDPTest() {
void ConfigureMotionTouch::OnConfigureTouchCalibration() {
ui->touch_calibration_config->setEnabled(false);
ui->touch_calibration_config->setText(tr("Configuring"));
CalibrationConfigurationDialog* dialog = new CalibrationConfigurationDialog(
CalibrationConfigurationDialog dialog(
this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
dialog->exec();
if (dialog->completed) {
min_x = dialog->min_x;
min_y = dialog->min_y;
max_x = dialog->max_x;
max_y = dialog->max_y;
dialog.exec();
if (dialog.completed) {
min_x = dialog.min_x;
min_y = dialog.min_y;
max_x = dialog.max_x;
max_y = dialog.max_y;
LOG_INFO(Frontend,
"UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
min_x, min_y, max_x, max_y);
@ -293,10 +297,11 @@ void ConfigureMotionTouch::OnConfigureTouchCalibration() {
}
void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
if (CanCloseDialog())
if (CanCloseDialog()) {
event->accept();
else
} else {
event->ignore();
}
}
void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
@ -342,16 +347,15 @@ bool ConfigureMotionTouch::CanCloseDialog() {
}
void ConfigureMotionTouch::ApplyConfiguration() {
if (!CanCloseDialog())
if (!CanCloseDialog()) {
return;
}
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
Common::ParamPackage motion_param{}, touch_param{};
Common::ParamPackage motion_param{};
motion_param.Set("engine", motion_engine);
touch_param.Set("engine", touch_engine);
if (motion_engine == "motion_emu") {
motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
} else if (motion_engine == "sdl") {
@ -359,6 +363,8 @@ void ConfigureMotionTouch::ApplyConfiguration() {
motion_param.Set("port", port);
}
Common::ParamPackage touch_param{};
touch_param.Set("engine", touch_engine);
if (touch_engine == "cemuhookudp") {
touch_param.Set("min_x", min_x);
touch_param.Set("min_y", min_y);

View File

@ -7,7 +7,7 @@
#include <memory>
#include <QDialog>
#include "common/param_package.h"
#include "core/settings.h"
#include "common/settings.h"
#include "input_common/main.h"
#include "input_common/udp/udp.h"
@ -26,11 +26,11 @@ class CalibrationConfigurationDialog : public QDialog {
public:
explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
u8 pad_index, u16 client_id);
~CalibrationConfigurationDialog();
~CalibrationConfigurationDialog() override;
private:
Q_INVOKABLE void UpdateLabelText(QString text);
Q_INVOKABLE void UpdateButtonText(QString text);
Q_INVOKABLE void UpdateLabelText(const QString& text);
Q_INVOKABLE void UpdateButtonText(const QString& text);
QVBoxLayout* layout;
QLabel* status_label;
@ -39,7 +39,10 @@ private:
// Configuration results
bool completed{};
u16 min_x, min_y, max_x, max_y;
u16 min_x{};
u16 min_y{};
u16 max_x{};
u16 max_y{};
friend class ConfigureMotionTouch;
};
@ -81,7 +84,10 @@ private:
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
// Coordinate system of the CemuhookUDP touch provider
int min_x, min_y, max_x, max_y;
int min_x{};
int min_y{};
int max_x{};
int max_y{};
bool udp_test_in_progress{};

View File

@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
#include <vector>
#include <QMessageBox>
#include <QPushButton>
#include <QString>
#include <fmt/format.h>
#include "citra_qt/configuration/config.h"
#include "citra_qt/configuration/configure_audio.h"
#include "citra_qt/configuration/configure_general.h"
#include "citra_qt/configuration/configure_graphics.h"
#include "citra_qt/configuration/configure_per_game.h"
#include "citra_qt/configuration/configure_system.h"
#include "citra_qt/util/util.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/loader/loader.h"
#include "core/loader/smdh.h"
#include "ui_configure_per_game.h"
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()),
filename{file_name.toStdString()}, title_id{title_id_}, system{system_} {
const auto config_file_name = title_id == 0 ? filename : fmt::format("{:016X}", title_id);
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
audio_tab = std::make_unique<ConfigureAudio>(this);
general_tab = std::make_unique<ConfigureGeneral>(this);
graphics_tab = std::make_unique<ConfigureGraphics>(this);
system_tab = std::make_unique<ConfigureSystem>(this);
ui->setupUi(this);
ui->tabWidget->addTab(general_tab.get(), tr("General"));
ui->tabWidget->addTab(system_tab.get(), tr("System"));
ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties"));
// remove Help question mark button from the title bar
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
scene = new QGraphicsScene;
ui->icon_view->setScene(scene);
if (system.IsPoweredOn()) {
QPushButton* apply_button = ui->buttonBox->addButton(QDialogButtonBox::Apply);
connect(apply_button, &QAbstractButton::clicked, this,
&ConfigurePerGame::HandleApplyButtonClicked);
}
connect(ui->button_reset_per_game, &QPushButton::clicked, this,
&ConfigurePerGame::ResetDefaults);
LoadConfiguration();
}
ConfigurePerGame::~ConfigurePerGame() = default;
void ConfigurePerGame::ResetDefaults() {
const auto config_file_name = title_id == 0 ? filename : fmt::format("{:016X}", title_id);
QMessageBox::StandardButton answer = QMessageBox::question(
this, tr("Citra"), tr("Are you sure you want to <b>reset your settings for this game</b>?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer == QMessageBox::No) {
return;
}
FileUtil::Delete(fmt::format("{}/custom/{}.ini",
FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir),
config_file_name));
close();
}
void ConfigurePerGame::ApplyConfiguration() {
general_tab->ApplyConfiguration();
system_tab->ApplyConfiguration();
graphics_tab->ApplyConfiguration();
audio_tab->ApplyConfiguration();
Settings::LogSettings();
game_config->Save();
}
void ConfigurePerGame::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigurePerGame::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigurePerGame::HandleApplyButtonClicked() {
ApplyConfiguration();
}
static QPixmap GetQPixmapFromSMDH(std::vector<u8>& smdh_data) {
Loader::SMDH smdh;
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
bool large = true;
std::vector<u16> icon_data = smdh.GetIcon(large);
const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
int size = large ? 48 : 24;
QImage icon(data, size, size, QImage::Format::Format_RGB16);
return QPixmap::fromImage(icon);
}
void ConfigurePerGame::LoadConfiguration() {
if (filename.empty()) {
return;
}
ui->display_title_id->setText(
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
const auto loader = Loader::GetLoader(filename);
std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
ui->display_name->setText(QString::fromStdString(title));
std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear();
QPixmap map = GetQPixmapFromSMDH(bytes);
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
ui->display_filepath->setText(QString::fromStdString(filename));
ui->display_format->setText(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
const auto valueText = ReadableByteSize(FileUtil::GetSize(filename));
ui->display_size->setText(valueText);
}

View File

@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
#include <QDialog>
#include <QList>
#include "citra_qt/configuration/config.h"
namespace Core {
class System;
}
class ConfigureAudio;
class ConfigureGeneral;
class ConfigureGraphics;
class ConfigureSystem;
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Ui {
class ConfigurePerGame;
}
class ConfigurePerGame : public QDialog {
Q_OBJECT
public:
// Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263
explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
Core::System& system_);
~ConfigurePerGame() override;
/// Loads all button configurations to settings file
void LoadConfiguration();
/// Save all button configurations to settings file
void ApplyConfiguration();
/// Reset the settings for this game
void ResetDefaults();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void HandleApplyButtonClicked();
std::unique_ptr<Ui::ConfigurePerGame> ui;
std::string filename;
u64 title_id;
QGraphicsScene* scene;
std::unique_ptr<Config> game_config;
Core::System& system;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureGeneral> general_tab;
std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureSystem> system_tab;
};

View File

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurePerGame</class>
<widget class="QDialog" name="ConfigurePerGame">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>661</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>900</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Info</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGraphicsView" name="icon_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="display_filepath">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>160</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="display_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Filepath</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title ID</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="display_title_id">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="display_name">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="display_format">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_reset_per_game">
<property name="text">
<string>Reset Game Settings</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="VerticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -6,8 +6,8 @@
#include <QFileDialog>
#include <QUrl>
#include "citra_qt/configuration/configure_storage.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_storage.h"
ConfigureStorage::ConfigureStorage(QWidget* parent)
@ -60,7 +60,7 @@ ConfigureStorage::ConfigureStorage(QWidget* parent)
ConfigureStorage::~ConfigureStorage() = default;
void ConfigureStorage::SetConfiguration() {
ui->nand_group->setVisible(Settings::values.use_custom_storage);
ui->nand_group->setVisible(Settings::values.use_custom_storage.GetValue());
QString nand_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
ui->nand_dir_path->setText(nand_path);
ui->open_nand_dir->setEnabled(!nand_path.isEmpty());
@ -71,8 +71,8 @@ void ConfigureStorage::SetConfiguration() {
ui->sdmc_dir_path->setText(sdmc_path);
ui->open_sdmc_dir->setEnabled(!sdmc_path.isEmpty());
ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd);
ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage);
ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd.GetValue());
ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage.GetValue());
ui->storage_group->setEnabled(!Core::System::GetInstance().IsPoweredOn());
}

View File

@ -4,12 +4,12 @@
#include <cstring>
#include <QMessageBox>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_system.h"
#include "citra_qt/uisettings.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/settings.h"
#include "ui_configure_system.h"
static const std::array<int, 12> days_in_month = {{
@ -249,10 +249,14 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)
// This scales across DPIs. (This value should be enough for "xxx%")
ui->clock_display_label->setMinimumWidth(40);
connect(ui->slider_clock_speed, &QSlider::valueChanged, [&](int value) {
connect(ui->slider_clock_speed, &QSlider::valueChanged, this, [&](int value) {
ui->clock_display_label->setText(QStringLiteral("%1%").arg(SliderToSettings(value)));
});
ui->clock_speed_label->setVisible(Settings::IsConfiguringGlobal());
ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
SetupPerGameUI();
ConfigureTime();
}
@ -261,11 +265,19 @@ ConfigureSystem::~ConfigureSystem() = default;
void ConfigureSystem::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock));
ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock.GetValue()));
QDateTime date_time;
date_time.setTime_t(Settings::values.init_time);
date_time.setTime_t(Settings::values.init_time.GetValue());
ui->edit_init_time->setDateTime(date_time);
long long init_time_offset = Settings::values.init_time_offset.GetValue();
long long days_offset = init_time_offset / 86400;
ui->edit_init_time_offset_days->setValue(days_offset);
unsigned long long time_offset = std::abs(init_time_offset) - std::abs(days_offset * 86400);
QTime time = QTime::fromMSecsSinceStartOfDay(time_offset * 1000);
ui->edit_init_time_offset_time->setTime(time);
if (!enabled) {
cfg = Service::CFG::GetModule(Core::System::GetInstance());
ASSERT_MSG(cfg, "CFG Module missing!");
@ -279,20 +291,31 @@ void ConfigureSystem::SetConfiguration() {
ui->label_disable_info->hide();
}
ui->slider_clock_speed->setValue(SettingsToSlider(Settings::values.cpu_clock_percentage));
ui->clock_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage));
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.cpu_clock_percentage.UsingGlobal()) {
ui->clock_speed_combo->setCurrentIndex(0);
ui->slider_clock_speed->setEnabled(false);
} else {
ui->clock_speed_combo->setCurrentIndex(1);
ui->slider_clock_speed->setEnabled(true);
}
ConfigurationShared::SetHighlight(ui->clock_speed_widget,
!Settings::values.cpu_clock_percentage.UsingGlobal());
}
ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds);
ui->slider_clock_speed->setValue(
SettingsToSlider(Settings::values.cpu_clock_percentage.GetValue()));
ui->clock_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue());
ui->plugin_loader->setChecked(Settings::values.plugin_loader_enabled.GetValue());
ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue());
}
void ConfigureSystem::ReadSystemSettings() {
// set username
username = cfg->GetUsername();
// TODO(wwylele): Use this when we move to Qt 5.5
// ui->edit_username->setText(QString::fromStdU16String(username));
ui->edit_username->setText(
QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));
ui->edit_username->setText(QString::fromStdU16String(username));
// set birthday
std::tie(birthmonth, birthday) = cfg->GetBirthday();
@ -329,32 +352,29 @@ void ConfigureSystem::ApplyConfiguration() {
bool modified = false;
// apply username
// TODO(wwylele): Use this when we move to Qt 5.5
// std::u16string new_username = ui->edit_username->text().toStdU16String();
std::u16string new_username(
reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
std::u16string new_username = ui->edit_username->text().toStdU16String();
if (new_username != username) {
cfg->SetUsername(new_username);
modified = true;
}
// apply birthday
int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
int new_birthday = ui->combo_birthday->currentIndex() + 1;
s32 new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
s32 new_birthday = ui->combo_birthday->currentIndex() + 1;
if (birthmonth != new_birthmonth || birthday != new_birthday) {
cfg->SetBirthday(new_birthmonth, new_birthday);
modified = true;
}
// apply language
int new_language = ui->combo_language->currentIndex();
s32 new_language = ui->combo_language->currentIndex();
if (language_index != new_language) {
cfg->SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
modified = true;
}
// apply sound
int new_sound = ui->combo_sound->currentIndex();
s32 new_sound = ui->combo_sound->currentIndex();
if (sound_index != new_sound) {
cfg->SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
modified = true;
@ -378,15 +398,30 @@ void ConfigureSystem::ApplyConfiguration() {
cfg->UpdateConfigNANDSavegame();
}
ConfigurationShared::ApplyPerGameSetting(&Settings::values.is_new_3ds, ui->toggle_new_3ds,
is_new_3ds);
Settings::values.init_clock =
static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
s64 time_offset_time = ui->edit_init_time_offset_time->time().msecsSinceStartOfDay() / 1000;
s64 time_offset_days = ui->edit_init_time_offset_days->value() * 86400;
if (time_offset_days < 0) {
time_offset_time = -time_offset_time;
}
Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
Settings::Apply();
Settings::values.init_time_offset = time_offset_days + time_offset_time;
Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
}
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo,
[this](s32) { return SliderToSettings(ui->slider_clock_speed->value()); });
}
void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
@ -394,10 +429,10 @@ void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
return;
// store current day selection
int birthday_index = ui->combo_birthday->currentIndex();
s32 birthday_index = ui->combo_birthday->currentIndex();
// get number of days in the new selected month
int days = days_in_month[birthmonth_index];
s32 days = days_in_month[birthmonth_index];
// if the selected day is out of range,
// reset it to 1st
@ -406,7 +441,7 @@ void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
// update the day combo box
ui->combo_birthday->clear();
for (int i = 1; i <= days; ++i) {
for (s32 i = 1; i <= days; ++i) {
ui->combo_birthday->addItem(QString::number(i));
}
@ -415,10 +450,10 @@ void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
}
void ConfigureSystem::ConfigureTime() {
ui->edit_init_time->setCalendarPopup(true);
QDateTime dt;
dt.fromString(QStringLiteral("2000-01-01 00:00:01"), QStringLiteral("yyyy-MM-dd hh:mm:ss"));
ui->edit_init_time->setMinimumDateTime(dt);
ui->edit_init_time->setCalendarPopup(true);
SetConfiguration();
@ -426,10 +461,16 @@ void ConfigureSystem::ConfigureTime() {
}
void ConfigureSystem::UpdateInitTime(int init_clock) {
const bool is_global = Settings::IsConfiguringGlobal();
const bool is_fixed_time =
static_cast<Settings::InitClock>(init_clock) == Settings::InitClock::FixedTime;
ui->label_init_time->setVisible(is_fixed_time);
ui->edit_init_time->setVisible(is_fixed_time);
ui->label_init_time->setVisible(is_fixed_time && is_global);
ui->edit_init_time->setVisible(is_fixed_time && is_global);
ui->label_init_time_offset->setVisible(!is_fixed_time && is_global);
ui->edit_init_time_offset_days->setVisible(!is_fixed_time && is_global);
ui->edit_init_time_offset_time->setVisible(!is_fixed_time && is_global);
}
void ConfigureSystem::RefreshConsoleID() {
@ -454,3 +495,50 @@ void ConfigureSystem::RefreshConsoleID() {
void ConfigureSystem::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureSystem::SetupPerGameUI() {
// Block the global settings if a game is currently running that overrides them
if (Settings::IsConfiguringGlobal()) {
ui->toggle_new_3ds->setEnabled(Settings::values.is_new_3ds.UsingGlobal());
ui->slider_clock_speed->setEnabled(Settings::values.cpu_clock_percentage.UsingGlobal());
return;
}
// Hide most settings for now, we can implement them later
ui->label_username->setVisible(false);
ui->label_birthday->setVisible(false);
ui->label_init_clock->setVisible(false);
ui->label_init_time->setVisible(false);
ui->label_console_id->setVisible(false);
ui->label_sound->setVisible(false);
ui->label_language->setVisible(false);
ui->label_country->setVisible(false);
ui->label_play_coins->setVisible(false);
ui->edit_username->setVisible(false);
ui->spinBox_play_coins->setVisible(false);
ui->combo_birthday->setVisible(false);
ui->combo_birthmonth->setVisible(false);
ui->combo_init_clock->setVisible(false);
ui->combo_sound->setVisible(false);
ui->combo_language->setVisible(false);
ui->combo_country->setVisible(false);
ui->label_init_time_offset->setVisible(false);
ui->edit_init_time_offset_days->setVisible(false);
ui->edit_init_time_offset_time->setVisible(false);
ui->button_regenerate_console_id->setVisible(false);
// Apps can change the state of the plugin loader, so plugins load
// to a chainloaded app with specific parameters. Don't allow
// the plugin loader state to be configured per-game as it may
// mess things up.
ui->label_plugin_loader->setVisible(false);
ui->plugin_loader->setVisible(false);
ui->allow_plugin_loader->setVisible(false);
connect(ui->clock_speed_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->slider_clock_speed->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
});
ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
is_new_3ds);
}

View File

@ -12,6 +12,10 @@ namespace Ui {
class ConfigureSystem;
}
namespace ConfigurationShared {
enum class CheckState;
}
namespace Service {
namespace CFG {
class Module;
@ -37,6 +41,9 @@ private:
void UpdateInitTime(int init_clock);
void RefreshConsoleID();
void SetupPerGameUI();
ConfigurationShared::CheckState is_new_3ds;
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>564</height>
<width>525</width>
<height>619</height>
</rect>
</property>
<property name="windowTitle">
@ -258,6 +258,44 @@
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDateTimeEdit" name="edit_init_time">
<property name="displayFormat">
<string>yyyy-MM-ddTHH:mm:ss</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_init_time_offset">
<property name="text">
<string>Offset time</string>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QGridLayout" name="edit_init_time_offset_grid">
<item column="0">
<widget class="QSpinBox" name="edit_init_time_offset_days">
<property name="suffix">
<string> days</string>
</property>
<property name="minimum">
<number>-2147483648</number>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
</widget>
</item>
<item column="1">
<widget class="QTimeEdit" name="edit_init_time_offset_time">
<property name="displayFormat">
<string>HH:mm:ss</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="9" column="1">
<widget class="QSpinBox" name="spinBox_play_coins">
<property name="maximum">
@ -295,13 +333,6 @@
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDateTimeEdit" name="edit_init_time">
<property name="displayFormat">
<string>yyyy-MM-ddTHH:mm:ss</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="toggle_new_3ds">
<property name="text">
@ -309,23 +340,64 @@
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_plugin_loader">
<property name="text">
<string>3GX Plugin Loader:</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="plugin_loader">
<property name="text">
<string>Enable 3GX plugin loader</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QCheckBox" name="allow_plugin_loader">
<property name="text">
<string>Allow games to change plugin loader state</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="group_advanced">
<property name="title">
<string>Advanced</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<widget class="QWidget" name="clock_speed_widget" native="true">
<layout class="QHBoxLayout" name="clock_speed_layout">
<property name="spacing">
<number>7</number>
</property>
<item>
<widget class="QComboBox" name="clock_speed_combo">
<item>
<property name="text">
<string>Use global clock speed</string>
</property>
</item>
<item>
<property name="text">
<string>Set clock speed:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="clock_speed_label">
<property name="text">
<string>CPU Clock Speed</string>
</property>
</widget>
</item>
<item row="0" column="2">
<item>
<widget class="QSlider" name="slider_clock_speed">
<property name="toolTip">
<string>&lt;html&gt;&lt;body&gt;Changes the emulated CPU clock frequency.&lt;br&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/body&gt;&lt;/html&gt;</string>
@ -353,7 +425,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item>
<widget class="QLabel" name="clock_display_label">
<property name="text">
<string/>
@ -366,6 +438,9 @@
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
</item>
@ -418,7 +493,6 @@
<tabstop>edit_init_time</tabstop>
<tabstop>spinBox_play_coins</tabstop>
<tabstop>button_regenerate_console_id</tabstop>
<tabstop>slider_clock_speed</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -73,11 +73,11 @@ ConfigureTouchFromButton::ConfigureTouchFromButton(
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), touch_maps(touch_maps),
selected_index(default_index), timeout_timer(std::make_unique<QTimer>()),
poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
binding_list_model = std::make_unique<QStandardItemModel>(0, 3, this);
binding_list_model->setHorizontalHeaderLabels({tr("Button"), tr("X"), tr("Y")});
ui->binding_list->setModel(binding_list_model.get());
binding_list_model = new QStandardItemModel(0, 3, this);
binding_list_model->setHorizontalHeaderLabels(
{tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
ui->binding_list->setModel(binding_list_model);
ui->bottom_screen->SetCoordLabel(ui->coord_label);
SetConfiguration();
@ -93,11 +93,12 @@ void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
// width values are not valid in the constructor
const int w =
ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
if (w > 0) {
if (w <= 0) {
return;
}
ui->binding_list->setColumnWidth(0, w);
ui->binding_list->setColumnWidth(1, w);
ui->binding_list->setColumnWidth(2, w);
}
}
void ConfigureTouchFromButton::SetConfiguration() {
@ -123,7 +124,7 @@ void ConfigureTouchFromButton::UpdateUiDisplay() {
QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
binding_list_model->appendRow({button, xcoord, ycoord});
int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
button->setData(dot, DataRoleDot);
}
}
@ -145,7 +146,7 @@ void ConfigureTouchFromButton::ConnectEvents() {
&ConfigureTouchFromButton::EditBinding);
connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
&ConfigureTouchFromButton::OnBindingSelection);
connect(binding_list_model.get(), &QStandardItemModel::itemChanged, this,
connect(binding_list_model, &QStandardItemModel::itemChanged, this,
&ConfigureTouchFromButton::OnBindingChanged);
connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
&ConfigureTouchFromButton::OnBindingDeleted);
@ -232,7 +233,7 @@ void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is
input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
const bool cancel) {
auto cell = binding_list_model->item(row_index, 0);
auto* cell = binding_list_model->item(row_index, 0);
if (cancel) {
if (is_new) {
binding_list_model->removeRow(row_index);
@ -260,15 +261,15 @@ void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is
}
void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
QStandardItem* button = new QStandardItem();
auto* button = new QStandardItem();
button->setEditable(false);
QStandardItem* xcoord = new QStandardItem(QString::number(pos.x()));
QStandardItem* ycoord = new QStandardItem(QString::number(pos.y()));
auto* x_coord = new QStandardItem(QString::number(pos.x()));
auto* y_coord = new QStandardItem(QString::number(pos.y()));
const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
button->setData(dot_id, DataRoleDot);
binding_list_model->appendRow({button, xcoord, ycoord});
binding_list_model->appendRow({button, x_coord, y_coord});
ui->binding_list->setFocus();
ui->binding_list->setCurrentIndex(button->index());
@ -283,11 +284,11 @@ void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
void ConfigureTouchFromButton::DeleteBinding() {
const int row_index = ui->binding_list->currentIndex().row();
if (row_index >= 0) {
ui->bottom_screen->RemoveDot(
binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
binding_list_model->removeRow(row_index);
if (row_index < 0) {
return;
}
ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
binding_list_model->removeRow(row_index);
}
void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
@ -329,7 +330,7 @@ void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
void ConfigureTouchFromButton::OnBindingDeleted([[maybe_unused]] const QModelIndex& parent,
int first, int last) {
for (int i = first; i <= last; ++i) {
auto ix = binding_list_model->index(i, 0);
const auto ix = binding_list_model->index(i, 0);
if (!ix.isValid()) {
return;
}
@ -422,7 +423,7 @@ int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
dot_font.setStyleHint(QFont::Monospace);
dot_font.setPointSize(20);
QLabel* dot = new QLabel(this);
auto* dot = new QLabel(this);
dot->setAttribute(Qt::WA_TranslucentBackground);
dot->setFont(dot_font);
dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
@ -440,13 +441,14 @@ int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
}
void TouchScreenPreview::RemoveDot(const int id) {
for (auto dot_it = dots.begin(); dot_it != dots.end(); ++dot_it) {
if (dot_it->first == id) {
dot_it->second->deleteLater();
dots.erase(dot_it);
const auto iter = std::find_if(dots.begin(), dots.end(),
[id](const auto& entry) { return entry.first == id; });
if (iter == dots.cend()) {
return;
}
}
iter->second->deleteLater();
dots.erase(iter);
}
void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
@ -470,14 +472,15 @@ void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
}
void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
for (const auto& dot : dots) {
if (dot.first == id) {
dot.second->setProperty(PropX, device_x);
dot.second->setProperty(PropY, device_y);
PositionDot(dot.second, device_x, device_y);
const auto iter = std::find_if(dots.begin(), dots.end(),
[id](const auto& entry) { return entry.first == id; });
if (iter == dots.cend()) {
return;
}
}
iter->second->setProperty(PropX, device_x);
iter->second->setProperty(PropY, device_y);
PositionDot(iter->second, device_x, device_y);
}
void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
@ -508,7 +511,7 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
}
const auto pos = MapToDeviceCoords(event->x(), event->y());
if (pos) {
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x(), pos->y()));
} else {
coord_label->clear();
}
@ -521,12 +524,13 @@ void TouchScreenPreview::leaveEvent([[maybe_unused]] QEvent* event) {
}
void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::MouseButton::LeftButton) {
if (event->button() != Qt::MouseButton::LeftButton) {
return;
}
const auto pos = MapToDeviceCoords(event->x(), event->y());
if (pos) {
emit DotAdded(*pos);
}
}
}
bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
@ -568,7 +572,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
if (coord_label) {
coord_label->setText(
QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
QStringLiteral("X: %1, Y: %2").arg(device_coord->x(), device_coord->y()));
}
}
return true;
@ -600,12 +604,17 @@ std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
const int device_y) const {
dot->move(static_cast<int>(
static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt()) *
(contentsRect().width() - 1) / (Core::kScreenBottomWidth - 1) +
contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f),
static_cast<int>(
static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt()) *
(contentsRect().height() - 1) / (Core::kScreenBottomHeight - 1) +
contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f));
const float device_coord_x =
static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
const int x_coord = static_cast<int>(
device_coord_x * (contentsRect().width() - 1) / (Core::kScreenBottomWidth - 1) +
contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
const float device_coord_y =
static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
const int y_coord = static_cast<int>(
device_coord_y * (contentsRect().height() - 1) / (Core::kScreenBottomHeight - 1) +
contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
dot->move(x_coord, y_coord);
}

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