Compare commits

...

336 Commits

Author SHA1 Message Date
c9942b35bb Revert "vk_instance: Always use geometry shaders if available"
This reverts commit 0a976f30c1.
2023-03-29 20:53:01 +03:00
ffe6904502 vk_master_semaphore: Remove timeout 2023-03-26 11:53:19 +03:00
fcf9d29a62 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-03-26 11:50:15 +03:00
9d7e68b7fc gl_shader_gen: Disable logging 2023-03-25 22:14:59 +02:00
0a976f30c1 vk_instance: Always use geometry shaders if available
* Fixes broken lighting in some games
2023-03-25 09:12:39 +02:00
8e05af4389 vk_instance: Blacklist broken extensions on newer qualcomm/arm drivers 2023-03-25 09:11:51 +02:00
d2b719ac5a vk_scheduler: split work queue waits and execution waits 2023-03-24 14:22:28 +02:00
4accbeba0d Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-03-24 11:17:21 +02:00
ee26b5b82f vk_shader_gen_spv: Return when gas mode is used
* Otherwise the shader compiler will crash since OpFunctionEnd was called
2023-03-13 18:55:04 +02:00
12a5265db1 citra_qt: Fix per-game graphics api option
* This was a pain to implement due to the sheer amount of combinations. I hope there aren't any bugs...
2023-03-12 00:06:54 +02:00
eb8b463ca8 Merge branch 'vulkan-2' of https://github.com/GPUCode/citra into vulkan-2 2023-03-11 20:46:54 +02:00
72c1785bf0 renderer_vulkan: Revert some recent present changes
* Fixes the deadlocking on mingw until I rewrite presentation again
2023-03-11 20:45:19 +02:00
a2aca3dde6 Merge branch 'master' into vulkan-2 2023-03-11 13:15:41 +02:00
c5f2267306 renderer_vulkan: Emulate 3 component formats when unsupported
* Using uscaled formats isn't enough for all targets, the steam deck for example still crashes. The previous approach involved breaking the attribute to vec2 + vec1 and combining them. This commit implements something much simpler; the attribute is replaced with the 4 component version and the shader then zeros the w component. Since the fixed binding is at least 16bytes and exists at the end of the vertex data, we always have adequate space to alias so tihs shouldn't break.
2023-03-09 16:02:21 +02:00
911fe5610c vk_renderpass_cache: Bring back flushes
* Idk why they were removed
2023-03-08 16:01:32 +02:00
b3e0078041 vk_resource_pool: Increase buffer descriptors 2023-03-08 15:20:10 +02:00
eeb1ff7965 rasterizer_cache: Avoid redundant texture copies when using texcubes 2023-03-08 10:11:47 +02:00
8721456944 renderer_vulkan: Use dynamic uniform buffers
* Reduce descriptor update overhead
2023-03-07 23:38:48 +02:00
a23fbae391 vk_resource_pool: Add back eSampledImage
* It's used by format reinterpreter
2023-03-06 22:28:31 +02:00
0a8f11ca63 renderer_vulkan: Small cleanups 2023-03-05 17:34:35 +02:00
8f194b5fff rasterizer_cache: Handle null surface id in GetTextureSurface
* Pokemon X tries to use a texture with zero width. The previous code handled this so will we
2023-03-05 16:36:19 +02:00
3b050668bb vk_texture_mailbox: Fix shutdown 2023-03-05 14:12:29 +02:00
d054eea0c4 rasterizer_cache: Handle null surface cubes properly 2023-03-05 14:05:22 +02:00
b9021ea469 renderer_vulkan: Address various sync issues
* Drop Common::SPSCQueue as it sometimes gives garbage frames.

* Reduce master semaphore timeout to allow the wait to recover in cases wher it gets stuck
2023-03-05 01:27:39 +02:00
223627c381 vk_scheduler: Prevent DispatchWork stalls
* Now that Finish no longer depends on the queue being empty of not we can move the chunk execution out of the work_mutex scope
2023-03-04 22:33:25 +02:00
ad4339464a renderer_vulkan: Submit present frames from the EmuThread
* This commit also reworks scheduler synchronization somewhat to be more reliable. Flush is given an atomic_bool is signal when the submit is done
2023-03-04 22:24:56 +02:00
32cb44d2b9 vk_descriptor_manager: Cache descriptor sets 2023-03-03 23:06:16 +02:00
39edca2cf7 vk_texture_runtime: Disable anisotropic filtering if unsupported 2023-03-03 22:47:27 +02:00
0a3acc25d2 ci: Bump macOS target to 11 (Big Sur) (#6325) 2023-03-03 22:47:27 +02:00
3f0bcf5913 Revert "ci: Disable uploading final macOS artifacts until ready to resume producing." 2023-03-03 22:47:27 +02:00
2384c8f811 vk_texture_runtime: Bring back FramebufferView
* Also move the usage hint flags to the allocation. If an allocation is used as framebuffer there's a high chance it will be used again as such. In addition is helps keeping pipeline barriers correct even when the surface is destoyed and recreated (happens often with framebuffers)
2023-03-03 16:28:01 +02:00
c26cb68a0c vk_renderpass_cache: Commonize setup between renderpass and dynamic rendering implementations
* Also fix small issue that caused broken rendering on the latter
2023-03-03 16:24:10 +02:00
c34bc45bf1 custom_tex_manager: Fix a race with async decoding 2023-03-02 16:20:32 +02:00
19617f32c8 custom_tex_manager: Multithread custom texture loading and decode
* Each texture has an atomic flag to signal to the backend when decoding is finished

* Don't store the file data as well to conserve RAM.
2023-03-02 16:20:32 +02:00
8396ce0b47 rasterizer_cache: Improve debugging
* Give surfaces an object label viewable in renderdoc with useful information

* Break up the Cache Mgmt microprofile scope and commonize it
2023-03-02 16:20:32 +02:00
07f85cf639 vk_shader_gen_spv: Emulate fog
* Fixes the intro of MM3D
2023-03-02 16:20:32 +02:00
dfd8ded206 vk_pipeline_cache: Improve async pipeline android fallback
* When VK_EXT_pipeline_creation_feedback is unavailable don't synchronously compile the pipeline, this is slow

* Compile the pipeline asynchrounsly instead and have the record thread wait for it. This way we can have multiple pipelines compiling at once
2023-03-02 16:20:32 +02:00
06caa535d6 rasterizer_cache: Remove usage of shared_ptr 2023-03-02 16:20:32 +02:00
42bed30b98 custom_tex_manager: Fix dumping issues
* Use the proper hash for dumping

* Add the mipmap as a postfix to help pack creators
2023-03-02 16:20:32 +02:00
74e75f1996 rasterizer_cache: More texture pack nonsense
* Some packs turn out have mipmaps but not the base level of a texture. Handle this to avoid black textures at a distance
2023-03-02 16:20:32 +02:00
f04a6a4d83 vk_rasterizer: Reduce stream buffer size
* 64MB is more than enough, any higher and it fills up the 256MB device local-host visible heap
2023-03-02 16:20:32 +02:00
33be1b744b rasterizer_cache: Limit mipmap skip to custom surfaces
* Fixes missing mipmaps when custom textures is enabled but the game does not have any pack

* Also fixes black textures in cases where a custom texture was not provided
2023-03-02 16:20:32 +02:00
2c9e0ec723 video_core: Add more ASTC formats 2023-03-02 16:20:32 +02:00
d1ac33b18b Revert "Revert "vk_scheduler: wait for command processing to complete""
* No longer needed after async present and causes sync issues during swapchain recreation

This reverts commit 0381081c5d.
2023-03-02 16:20:32 +02:00
c00bdc4214 gl_texture_runtime: Generate all mipmaps 2023-03-02 16:20:32 +02:00
74be64b60a vk_stream_buffer: Fix synchronization during buffer overflow
* Especially with custom textures the bufer would fill up but didn't wait any watchers on overflow overriding some textures
2023-03-02 16:20:32 +02:00
11ca327951 renderer_vulkan: Optimize stream buffer usage
* Use vma for allocating the memory. In addition place upload/download buffers on the cpu, they don't need to be device local
2023-03-02 16:20:32 +02:00
631da59392 renderer_vulkan: Allow vsync change during gameplay 2023-03-02 16:20:32 +02:00
e861c456c9 custom_tex: Fix hash read on windows 2023-03-02 16:20:31 +02:00
c3ab060576 externals: Trim down zlib-ng build 2023-03-02 16:20:31 +02:00
94ee7c68fc video_core: Clear runtime on rasterizer cache flush
* When changing res scale or custom textures a large number of textures will be replaced so better not keep the old ones
2023-03-02 16:20:31 +02:00
8e8097e7c0 renderer_vulkan: Use combined image samplers
* Less descriptors = good
2023-03-02 16:20:31 +02:00
52683adedd video_core: Rewrite custom textures 2023-03-02 16:20:31 +02:00
e47c47245f rasterizer_cache: Add support for texture dumping 2023-03-02 16:20:31 +02:00
004b83c978 rasterizer_cache: Fix surface validation
* Sometimes the copy interval might be larger than the validation interval
2023-03-02 16:20:31 +02:00
4bff44c9a9 common: Move image decoders to common
* Replace the various image interfaces with spng which is very lightweight and fast. Also add a dds header which will be useful when support for that format is implemented
2023-03-02 16:20:31 +02:00
18ff007ff2 rasterizer_cache: Remove expanded surface immediately
* Since allocations are recycled under the hood there's not risk of a texture being in use
2023-03-02 16:20:31 +02:00
e41ceb3c88 rasterizer_cache: Explicit type in traits 2023-03-02 16:20:31 +02:00
14880862bc video_core: Cleanup surface interface
* Remove unused FramebufferView and make the opengl handles private
2023-03-02 16:20:31 +02:00
e651bb41bc video_core: Cleanup microprofiles
* Remove upload/download targets these are covered by the rasterizer cache
2023-03-02 16:20:31 +02:00
8e93039ef9 rasterizer_cache: Support multi-level surfaces
* With this commit the cache can now directly upload and use mipmaps
  without needing to sync them with watchers. By using native mimaps
  directly this also adds support for mipmap for cube

* Since watchers have been removed texture cubes still work but are uncached
  so slower as well. Will be fixed soon.
2023-03-02 16:20:31 +02:00
65b02f35c7 rasterizer_cache: Cleanup texture clears 2023-03-02 16:20:31 +02:00
d42ccb387a rasterizer_cache: Wrap common draw operation in FramebufferBase
* Makes the rasterizer draw method much cleaner
2023-03-02 16:20:31 +02:00
ee0a7476b3 rasterizer_cache: Simplify SurfaceBase
* The casts are bit ugly but will be refactored soon
2023-03-02 16:20:30 +02:00
45ffc56733 video_core: Manage samplers in the rasterizer cache 2023-03-02 16:20:30 +02:00
be8ee3d706 rasterizer_cache: Remove BlitSurfaces
* Choose copy or blit based on the caller instead
2023-03-02 16:20:30 +02:00
ab688170f0 rasterizer_cache: Commonize texture acceleration functions
* They don't contain any backend specific code so don't duplicate them
2023-03-02 16:20:30 +02:00
274f6093f3 rasterizer_cache: Move microprofile to the top 2023-03-02 16:20:30 +02:00
9662425337 rasterizer_cache: Remove Invalid match flags
* It was specified in every FindMatch. Only copy did not specify it, but copy surfaces ignore the flag regardless making it superflous
2023-03-02 16:20:30 +02:00
6fde9b3354 rasterizer_cache: Switch to page table
* Should be more efficient for surface storage than the interval map
2023-03-02 16:20:30 +02:00
e5907f0979 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-03-02 16:19:54 +02:00
2855d30815 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-02-26 21:33:52 +02:00
546c7b3bfd Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-02-19 16:05:11 +02:00
41c10cd5a7 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-02-16 12:59:33 +02:00
bc77e16653 rasterizer_cache: Check levels when finding exact match
* Fixes validation errors in multiple games
2023-02-11 17:54:42 +02:00
6ba83db6dd externals: Switch back to upstream dynarmic
* Thanks mary for the quickfix
2023-02-11 17:29:55 +02:00
96effa46e4 vk_pipeline_cache: Reduce flickering on android 2023-02-11 13:17:32 +02:00
e7a1318773 externals: Switch to older dynarmic
* Upstream has lag issues
2023-02-11 13:17:12 +02:00
ffc9e34281 vk_rasterizer: Fix broken software shaders 2023-02-11 00:29:04 +02:00
20fc09df13 externals: Update dynarmic 2023-02-11 00:15:24 +02:00
6c78abb015 android: Perform AGP upgrades 2023-02-10 22:13:34 +02:00
0d3434734a vk_rasterizer: Skip draw if no attachments 2023-02-10 21:58:06 +02:00
42a6f7a42e vk_renderpass_cache: Flush on qcom as well 2023-02-10 21:53:13 +02:00
95e6428d33 config: Remove async recording option
* There's no reason to turn this off aside from debugging. So use renderer_debug to to deduce whether to use a worker thread or not
2023-02-10 16:35:23 +02:00
d9ed4600ca video_core: Fix some struct formatting 2023-02-10 16:14:18 +02:00
612647f94f video_core: Only allocate needed levels
* Especially with high res scaling allocating so many levels increases memory usage. Also clamp level size to 8x8, since on  tiled textures it doesn't make sense to have any smaller than that. Fixes portal3DS and log spam on ZLBW
2023-02-10 16:07:55 +02:00
6a16a8f60d android: Enable async shaders by default 2023-02-09 17:50:49 +02:00
1ffd9f08af surface_params: Cleanup 2023-02-09 17:11:28 +02:00
c855d84492 texture_codec: Use namespace Color
* Makes the code somewhat cleaner
2023-02-09 17:04:22 +02:00
d461778296 vk_rasterizer: Small cleanup 2023-02-09 16:08:40 +02:00
3fe0130fdb vk_instance: Enable image view format reinterpretation 2023-02-09 14:41:42 +02:00
b0880f0ef8 video_core: Move rasterizer cache template definitions to separate file
* Helps with build times
2023-02-06 22:52:18 +02:00
0bb2e8c618 renderer_vulkan: More strict barriers 2023-02-06 22:30:34 +02:00
84ccb45c5c vk_rasterizer: Move vertex array setup to AccelerateDrawBatch
* Avoids state invalidating due to scheduler flushes
2023-02-06 21:34:24 +02:00
f8b853d001 video_core: Limit g_state usage in rasterizers
* Makes the code cleaner and should help in future refactoring endeavors
2023-02-06 20:46:43 +02:00
61e0725d9d renderer_vulkan: Barrier frame attachment 2023-02-06 18:24:40 +02:00
2b9ab33af3 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-02-05 21:57:29 +02:00
09350f71e8 renderer_vulkan: Fix present mailbox shutdown sequence 2023-02-05 21:56:12 +02:00
582c438441 vk_rasterizer: Properly set fixed attrib offset
* There was a small chance the main attrib exactly fit the buffer which caused the fixed offset to be 0x8000000 and crash
2023-02-05 21:09:54 +02:00
bc10681156 renderer_vulkan: Cleanup renderpass code 2023-02-05 18:42:29 +02:00
faefc5cfe1 renderer_vulkan: Use separate present thread 2023-02-05 18:29:49 +02:00
fd09f1471e common: Make equality operators explicit for Rectangle 2023-02-04 20:01:19 +02:00
c1c68a3487 renderer_vulkan: Move microprofile defines to top of file 2023-02-04 20:01:19 +02:00
5849d29fc1 video_core: Move RendererBase to VideoCore 2023-02-04 20:01:19 +02:00
b31b6e35a2 renderer_opengl: Rewrite stream buffer
* New implementation is based on Dolphin's MapAndSync and BufferStorage buffers.
  Replacing orphaning with syncrhonization which should make it much faster than before.

* Texture downloads don't use PBO anymore since it didn't offer any speed benefits. Finally
  a bug was fixed that only affected the glBufferData fallback path and should fix android/gles
2023-02-04 20:01:16 +02:00
79a53c8003 video_core: Remove Init method
* The constructor can do the same
2023-02-04 13:08:42 +02:00
fd9525acc2 video_core: Use interval map for cached_pages
* Also eliminate g_memory in hardware renderers
2023-02-04 12:57:57 +02:00
938ec204f5 qt: Fix paintEvent not being called until window resize on macOS. (#25)
* qt: Remove need for AppleSurfaceHelper.

* qt: Fix paintEvent not being called until window resize on macOS.
2023-02-02 15:41:41 +02:00
7c8bfbb078 Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2 2023-02-01 23:27:12 +02:00
d7bf139e85 renderer_vulkan: Add pipeline barriers for attachments 2023-02-01 23:26:44 +02:00
df7f1b13cb polyfill_thread: satisfy execution ordering requirements of stop_callback 2023-02-01 23:26:44 +02:00
e08e644e73 video_core: Move present globals to RendererSettings 2023-02-01 23:26:44 +02:00
ccf36b5e25 video_core: Remove global screenshot state 2023-02-01 23:26:44 +02:00
b6427d0ee0 renderer_vulkan: Cleanup code 2023-02-01 23:26:44 +02:00
5c401b8ea0 renderer_vulkan: Async presentation
* This rewrites a large portion of the presentation engine to be more thread safe
  and moves all swapchain usage to the presentation thread. Previously acquires were
  done on the main thread which required the next frame to wait for the previous one
  to finish presenting
* The new implementation is based on the OpenGL mailbox system, simplified. The screens
  are drawn on separate render frames that get sent to the presentation thread to be
  presented. Queue access is now thread safe as well.
2023-02-01 23:26:44 +02:00
bd3571db5a vk_blit_helper: Fix android crashes 2023-02-01 23:26:44 +02:00
b18710e4df renderer_vulkan: Emulate depth-stencil blits with VK_EXT_shader_stencil_export
* Should fix depth blits on AMD
2023-02-01 23:26:44 +02:00
69b66cb41d renderer_vulkan: Implement VK_KHR_dynamic_rendering
* Removes the need for framebuffers/renderpass on desktop
2023-01-27 16:35:58 +02:00
a5f86e9813 renderer_vulkan: Move framebuffer handling to the renderpass cache 2023-01-27 00:27:26 +02:00
920492925c vk_shader_gen: Disable clip plane 1 when unused
* Fixes Star Fox 3D missing half the screen
2023-01-26 22:30:27 +02:00
0381081c5d Revert "vk_scheduler: wait for command processing to complete"
* This causes DispatchWork to stall and everything that calls it resulting in noticeable slowdown
2023-01-26 22:05:38 +02:00
382b64d3c0 surface_base: Fix issue with GetCopyableInterval 2023-01-26 15:04:15 +02:00
a629aa0dde renderer_vulkan: Check for VK_EXT_pipeline_creation_feedback
* Unused at the moment but will be useful later
2023-01-26 14:32:58 +02:00
b29f263e1b renderer_vulkan: Support additional dynamic states 2023-01-25 22:46:35 +02:00
48d3093fc8 vk_instance: Rework feature management
* Use a helper macro to always check for features. Previously it was assumed extension availability was also feature availability but this isn't the case. This is inspired by skyline's trait_manager
2023-01-25 22:46:10 +02:00
f77491196c vk_renderpass_cache: Create renderpasses on demand 2023-01-25 22:41:50 +02:00
0fce3e556f externals: Update vulkan-headers 2023-01-25 00:29:47 +02:00
12e69913c2 renderer_vulkan: Rework attribute format handling
* Centralize format support query to the instance similar to pixel formats
  In addition drop the component splitting. Favour scaled formats which don't require
  any shader casting to work and fallback to uint if necessary. Using scaled formats
  also has the benefit of reducing vertex shader permutations.
2023-01-25 00:25:08 +02:00
89217f8c4b vk_shader_gen_spv: Fix scissor test 2023-01-24 22:17:05 +02:00
247b6900c7 vk_swapchain: Fix incorrect image_count
* Sometimes the swapchain won't create as many images as requested. Adjust image_count for fix that
2023-01-23 23:30:06 +02:00
1e42a40640 vk_shader_gen: Remove defines
* Causes shader compiler errors
2023-01-22 23:29:54 +02:00
7eab7b4151 android: Expose async shaders in the GUI 2023-01-22 10:33:19 +02:00
e9ccd51286 renderer_vulkan: Trim down used features 2023-01-22 10:33:19 +02:00
9f385ddeb7 vk_instance: Only check the portability extension on apple platforms 2023-01-22 10:33:19 +02:00
ab73566acc renderer_vulkan: Add suport for VK_EXT_debug_report
* Used in older android devices
2023-01-22 10:33:19 +02:00
1e3971038f vk_swapchain: Create semaphores at swapchain creation time
* Sometimes minImageCount is higher than 3 and that caused crashes
2023-01-22 10:33:19 +02:00
306943532c renderer_vulkan: Preliminary screenshot support
* It's broken for now but at least it doesn't crash
2023-01-22 10:33:19 +02:00
27698b0c93 vk_instance: Manually enable features
* We need these features so better force enable them and have the driver crash if they're unavailable.
2023-01-22 10:33:18 +02:00
d6cab3ab40 renderer_vulkan: Flush scheduler on renderpass end
* Follows the Mali guide recommendation and fixes performance regression introduced by async shaders commit
2023-01-22 10:33:18 +02:00
cbd9a6ffe3 vk_swapchain: Prefer scheduler finish
* The scheduler might have recorded a present that uses the to-be-destroyed semaphores. vkWaitIdle might miss this
2023-01-22 10:33:18 +02:00
4752818920 renderer_vulkan: Async shaders 2023-01-22 10:33:18 +02:00
131129062b ci: Fix macOS script permissions. (#24) 2023-01-17 20:47:40 +02:00
fad1ee7140 Merge branch 'master' into vulkan-2 2023-01-17 18:39:04 +02:00
b9ca7bef3f rasterizer_cache: Handle texture swizzle case with unaligned offsets in same tile. (#23) 2023-01-17 13:42:47 +02:00
0e0771ad74 Merge from latest upstream (#22)
* externals: bump xbyak to v6.68

* externals: point to upstream dynarmic

* build: Update to support multi-arch builds.

* ci: Generate universal macOS build.

* macOS: Make Citra show up in the Launchpad Games folder (#6245)

* Instead of there being an "Abort/Continue" prompt when a savestate fails to save or load, it just brings up a warning box. (#6236)

* This fixes #6041 by changing OnCoreError. Instead of there being an "Abort/Continue" prompt when a savestate fails to save or load, it just brings up a warning box.

I also changed "Abort/Continue" to "Quit Game/Continue" for better clarity

* Fixed formatting

* externals: Switch to newer cryptopp-cmake. (#6242)

* Implement svcGetHandleInfo, svcOpenProcess/Thread, svcGetProcessList (#6243)

* Implement svcGetHandleInfo, svcOpenProcess/Thread, svcGetProcessList

* Apply suggestions

* Add comment to stubbed enum values in svcGetHandleInfo

* Revert u32 -> size_t

Co-authored-by: SachinVin <sachinvinayak2000@gmail.com>
Co-authored-by: SachinVin <26602104+SachinVin@users.noreply.github.com>
Co-authored-by: UltraHDR <108294295+UltraHDR@users.noreply.github.com>
Co-authored-by: upadsamay387 <56898833+upadsamay387@users.noreply.github.com>
Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2023-01-17 13:11:41 +02:00
489248e77f video_core: De-duplicate texture format conversion logic. (#21)
* video_core: De-duplicate texture format conversion logic.

* video_core: Replace std::byte with u8 and remove excess linear texture converters.

* video_core: Remove implicit RGBA conversions from convert table for now, add comments explaining omissions.
2023-01-13 13:54:42 +02:00
f593268476 rasterizer_cache: Add converted swizzle mapping for D24 and log error for missing (un)swizzle functions. (#20) 2023-01-13 00:51:31 +02:00
8fcc3d2121 rasterizer_cache: Add morton conversion for D24 <-> D32 (#19) 2023-01-12 13:33:12 +02:00
a3a8964d46 renderer_vulkan: Centralize pixel format trait management. (#18)
* renderer_vulkan: Centralize pixel format trait management.

* renderer_vulkan: Add D24 <-> D32 conversion support.
2023-01-12 10:43:40 +02:00
c8d614139c vk_shader_gen_spv: Implement scissor testing
* Fixes weird looking map in LBW
2023-01-11 22:20:53 +02:00
25fe723fa6 renderer_vulkan: Support self copy
FE games copy parts of the framebuffer to itself. Don't switch layout in that case
2023-01-11 20:48:20 +02:00
2335b47f4b code: Run clang format 2023-01-11 20:48:20 +02:00
10a7b60b12 ci: Fix android builds 2023-01-11 20:48:20 +02:00
98ab3c9610 common: Log more vulkan settings 2023-01-11 20:48:20 +02:00
0ca25b64e1 renderer_vulkan: Improve storage reinterpretation barriers 2023-01-11 20:48:20 +02:00
694e49b857 renderer_vulkan: Remove master semaphore fence
* Wasn't used anywhere
2023-01-11 20:48:20 +02:00
381db8452b renderer_vulkan: Gate reduced shadow binding count to Android only. (#17) 2023-01-11 15:03:11 +02:00
e2076f2385 shader: Handle out-of-bounds uniform access via address register. (#16) 2023-01-10 13:23:39 +02:00
9c206db630 video_core: Move some common state management out of specific render backends. (#15) 2023-01-09 19:28:18 +02:00
447f29285f renderer_vulkan: Add fence implementation of MasterSemaphore 2023-01-08 15:04:28 +02:00
26a9002d97 vk_swapchain: Lower image count to 3 2023-01-08 13:57:00 +02:00
a12748d79e vk_shader_gen_spv: Emulate logic ops 2023-01-08 13:02:23 +02:00
5abfdff66a android: Query window size from surface 2023-01-08 00:55:41 +02:00
8a315d0c8f vk_rasterizer: Initialize uniform buffers
* Pokemon Y crashes without this
2023-01-07 23:51:33 +02:00
4ee36e05b6 renderer_vulkan: Proper barriers on renderpass clear 2023-01-07 23:12:56 +02:00
11061f36e6 renderer_vulkan: Surface recreation works 2023-01-06 21:15:52 +02:00
6d286d5f8c vk_instance: Avoid enabling debug messenger when unsupported 2023-01-06 20:29:54 +02:00
4619ed086c android: Add new graphics API options to GUI 2023-01-06 14:39:50 +02:00
3843122cf8 video_core: Update usage of tex_lod_bias 2023-01-06 09:37:11 +02:00
d320eef663 Fix opengl and auto resolution crashes (#14)
* video_core: fix UniformData size on opengl

* video_core: check for renderer on auto resolution

The rasterizer cache constructor will call GetResolutionScaleFactor
before the renderer is initialized on the vulkan backend, so
check for that case and return 1 as a placeholder scale factor.
2023-01-06 09:05:21 +02:00
a7a9b94a30 common: Update thread library from yuzu 2023-01-05 17:34:47 +02:00
a9f2a69487 renderer_vulkan: Remove dead code 2023-01-05 17:06:40 +02:00
78965ba18d ci: Install macOS Vulkan dependencies from official SDK release. (#13) 2023-01-05 10:03:43 +02:00
52c41d185b renderer_vulkan: Set up and configure VK_KHR_portability_subset extension according to spec. (#12)
* renderer_vulkan: Set up and configure VK_KHR_portability_subset extension according to spec.

* renderer_vulkan: Move mipmap LOD bias to shaders for compatibility.
2023-01-04 10:43:08 +02:00
c5c041de89 Merge branch 'citra-emu:master' into vulkan-2 2023-01-04 00:44:52 +02:00
e46a88f24a vulkan: Align vertex strides according to portability subset requirements. (#11) 2023-01-03 00:36:06 +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
252 changed files with 54445 additions and 9125 deletions

View File

@ -5,6 +5,7 @@ pip3 install macpack
export FFMPEG_VER=5.1
export QT_VER=5.15.8
export VULKAN_SDK_VER=1.3.236.0
mkdir tmp
cd tmp/
@ -18,3 +19,9 @@ cp -rv $(pwd)/ffmpeg-${FFMPEG_VER}/* /
wget https://github.com/SachinVin/ext-macos-bin/raw/main/qt/qt-${QT_VER}.7z
7z x qt-${QT_VER}.7z
sudo cp -rv $(pwd)/qt-${QT_VER}/* /usr/local/
# install Vulkan SDK
wget https://sdk.lunarg.com/sdk/download/1.3.236.0/mac/vulkansdk-macos-${VULKAN_SDK_VER}.dmg
hdiutil attach vulkansdk-macos-${VULKAN_SDK_VER}.dmg
sudo /Volumes/vulkansdk-macos-${VULKAN_SDK_VER}/InstallVulkan.app/Contents/MacOS/InstallVulkan install --accept-licenses --confirm-command --default-answer com.lunarg.vulkan.core com.lunarg.vulkan.usr
hdiutil detach /Volumes/vulkansdk-macos-${VULKAN_SDK_VER}

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
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_FRAMEWORKS_PATH="$BUNDLE_CONTENTS_PATH/Frameworks"
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
macdeployqt $BUNDLE_PATH -executable=$BUNDLE_EXECUTABLE_PATH
# move libs into folder for deployment
macpack $CITRA_STANDALONE_PATH -d "libs"
# bundle MoltenVK
VULKAN_SDK_PATH=~/VulkanSDK/*/macOS
cp $VULKAN_SDK_PATH/lib/libvulkan.dylib $BUNDLE_FRAMEWORKS_PATH
cp $VULKAN_SDK_PATH/lib/libMoltenVK.dylib $BUNDLE_FRAMEWORKS_PATH
cp $VULKAN_SDK_PATH/lib/libVkLayer_*.dylib $BUNDLE_FRAMEWORKS_PATH
cp -r $VULKAN_SDK_PATH/share/vulkan $BUNDLE_RESOURCES_PATH
find $BUNDLE_RESOURCES_PATH/vulkan -type f -name "*.json" -exec sed -i'' -e 's/..\/..\/..\/lib/..\/..\/..\/Frameworks/g' {} \;
install_name_tool -add_rpath "@loader_path/../Frameworks/" $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

@ -130,11 +130,11 @@ jobs:
run: ./.ci/macos/universal.sh
env:
ARTIFACTS: macos-x86_64 macos-arm64
# - name: Upload
# uses: actions/upload-artifact@v3
# with:
# name: macos
# path: artifacts/
- name: Upload
uses: actions/upload-artifact@v3
with:
name: macos
path: artifacts/
- name: Delete intermediate artifacts
uses: geekyeggo/delete-artifact@v2
with:
@ -162,6 +162,14 @@ jobs:
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
@ -197,6 +205,9 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install ccache apksigner -y
sudo add-apt-repository -y ppa:savoury1/ffmpeg4
sudo apt-get update -y
sudo apt-get install --no-install-recommends -y glslang-dev glslang-tools
- name: Build
run: ./.ci/android/build.sh
- name: Copy and sign artifacts

30
.gitmodules vendored
View File

@ -43,9 +43,6 @@
[submodule "teakra"]
path = externals/teakra
url = https://github.com/wwylele/teakra.git
[submodule "lodepng"]
path = externals/lodepng/lodepng
url = https://github.com/lvandeve/lodepng.git
[submodule "zstd"]
path = externals/zstd
url = https://github.com/facebook/zstd.git
@ -53,11 +50,26 @@
path = externals/libyuv
url = https://github.com/lemenkov/libyuv.git
[submodule "sdl2"]
path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL
path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL
[submodule "cryptopp-cmake"]
path = externals/cryptopp-cmake
url = https://github.com/abdes/cryptopp-cmake.git
path = externals/cryptopp-cmake
url = https://github.com/abdes/cryptopp-cmake.git
[submodule "cryptopp"]
path = externals/cryptopp
url = https://github.com/weidai11/cryptopp.git
path = externals/cryptopp
url = https://github.com/weidai11/cryptopp.git
[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
[submodule "zlib-ng"]
path = externals/zlib-ng/zlib-ng
url = https://github.com/zlib-ng/zlib-ng

View File

@ -9,6 +9,7 @@ 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)

View File

@ -1,3 +1,17 @@
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;
}
QPushButton#3DOptionStatusBarButton {
color: #A5A5A5;
font-weight: bold;

View File

@ -1237,6 +1237,20 @@ 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;
}
QPushButton#3DOptionStatusBarButton {
color: #A5A5A5;
font-weight: bold;

View File

@ -89,6 +89,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)
@ -116,6 +129,9 @@ if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2)
endif()
# Zlib
add_subdirectory(zlib-ng)
# Zstandard
set(ZSTD_LEGACY_SUPPORT OFF)
set(ZSTD_BUILD_PROGRAMS OFF)
@ -174,8 +190,8 @@ if (ENABLE_WEB_SERVICE)
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
endif()
# lodepng
add_subdirectory(lodepng)
# libspng
add_subdirectory(libspng)
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID)
@ -183,3 +199,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)

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

14
externals/libspng/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,14 @@
add_library(spng STATIC spng.h spng.c)
target_compile_definitions(spng PUBLIC SPNG_STATIC)
target_include_directories(spng PUBLIC ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(spng PRIVATE ZLIB::ZLIB)
# Enable SSE4.1 on x64
if ("x86_64" IN_LIST ARCHITECTURE)
target_compile_definitions(spng PRIVATE SPNG_SSE=4)
if (NOT MSVC)
target_compile_options(spng PRIVATE -msse4.1)
endif()
endif()
add_library(spng::spng ALIAS spng)

6979
externals/libspng/spng.c vendored Normal file

File diff suppressed because it is too large Load Diff

537
externals/libspng/spng.h vendored Normal file
View File

@ -0,0 +1,537 @@
/* SPDX-License-Identifier: BSD-2-Clause */
#ifndef SPNG_H
#define SPNG_H
#ifdef __cplusplus
extern "C" {
#endif
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC)
#if defined(SPNG__BUILD)
#define SPNG_API __declspec(dllexport)
#else
#define SPNG_API __declspec(dllimport)
#endif
#else
#define SPNG_API
#endif
#if defined(_MSC_VER)
#define SPNG_CDECL __cdecl
#else
#define SPNG_CDECL
#endif
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#define SPNG_VERSION_MAJOR 0
#define SPNG_VERSION_MINOR 7
#define SPNG_VERSION_PATCH 3
enum spng_errno
{
SPNG_IO_ERROR = -2,
SPNG_IO_EOF = -1,
SPNG_OK = 0,
SPNG_EINVAL,
SPNG_EMEM,
SPNG_EOVERFLOW,
SPNG_ESIGNATURE,
SPNG_EWIDTH,
SPNG_EHEIGHT,
SPNG_EUSER_WIDTH,
SPNG_EUSER_HEIGHT,
SPNG_EBIT_DEPTH,
SPNG_ECOLOR_TYPE,
SPNG_ECOMPRESSION_METHOD,
SPNG_EFILTER_METHOD,
SPNG_EINTERLACE_METHOD,
SPNG_EIHDR_SIZE,
SPNG_ENOIHDR,
SPNG_ECHUNK_POS,
SPNG_ECHUNK_SIZE,
SPNG_ECHUNK_CRC,
SPNG_ECHUNK_TYPE,
SPNG_ECHUNK_UNKNOWN_CRITICAL,
SPNG_EDUP_PLTE,
SPNG_EDUP_CHRM,
SPNG_EDUP_GAMA,
SPNG_EDUP_ICCP,
SPNG_EDUP_SBIT,
SPNG_EDUP_SRGB,
SPNG_EDUP_BKGD,
SPNG_EDUP_HIST,
SPNG_EDUP_TRNS,
SPNG_EDUP_PHYS,
SPNG_EDUP_TIME,
SPNG_EDUP_OFFS,
SPNG_EDUP_EXIF,
SPNG_ECHRM,
SPNG_EPLTE_IDX,
SPNG_ETRNS_COLOR_TYPE,
SPNG_ETRNS_NO_PLTE,
SPNG_EGAMA,
SPNG_EICCP_NAME,
SPNG_EICCP_COMPRESSION_METHOD,
SPNG_ESBIT,
SPNG_ESRGB,
SPNG_ETEXT,
SPNG_ETEXT_KEYWORD,
SPNG_EZTXT,
SPNG_EZTXT_COMPRESSION_METHOD,
SPNG_EITXT,
SPNG_EITXT_COMPRESSION_FLAG,
SPNG_EITXT_COMPRESSION_METHOD,
SPNG_EITXT_LANG_TAG,
SPNG_EITXT_TRANSLATED_KEY,
SPNG_EBKGD_NO_PLTE,
SPNG_EBKGD_PLTE_IDX,
SPNG_EHIST_NO_PLTE,
SPNG_EPHYS,
SPNG_ESPLT_NAME,
SPNG_ESPLT_DUP_NAME,
SPNG_ESPLT_DEPTH,
SPNG_ETIME,
SPNG_EOFFS,
SPNG_EEXIF,
SPNG_EIDAT_TOO_SHORT,
SPNG_EIDAT_STREAM,
SPNG_EZLIB,
SPNG_EFILTER,
SPNG_EBUFSIZ,
SPNG_EIO,
SPNG_EOF,
SPNG_EBUF_SET,
SPNG_EBADSTATE,
SPNG_EFMT,
SPNG_EFLAGS,
SPNG_ECHUNKAVAIL,
SPNG_ENCODE_ONLY,
SPNG_EOI,
SPNG_ENOPLTE,
SPNG_ECHUNK_LIMITS,
SPNG_EZLIB_INIT,
SPNG_ECHUNK_STDLEN,
SPNG_EINTERNAL,
SPNG_ECTXTYPE,
SPNG_ENOSRC,
SPNG_ENODST,
SPNG_EOPSTATE,
SPNG_ENOTFINAL,
};
enum spng_text_type
{
SPNG_TEXT = 1,
SPNG_ZTXT = 2,
SPNG_ITXT = 3
};
enum spng_color_type
{
SPNG_COLOR_TYPE_GRAYSCALE = 0,
SPNG_COLOR_TYPE_TRUECOLOR = 2,
SPNG_COLOR_TYPE_INDEXED = 3,
SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4,
SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6
};
enum spng_filter
{
SPNG_FILTER_NONE = 0,
SPNG_FILTER_SUB = 1,
SPNG_FILTER_UP = 2,
SPNG_FILTER_AVERAGE = 3,
SPNG_FILTER_PAETH = 4
};
enum spng_filter_choice
{
SPNG_DISABLE_FILTERING = 0,
SPNG_FILTER_CHOICE_NONE = 8,
SPNG_FILTER_CHOICE_SUB = 16,
SPNG_FILTER_CHOICE_UP = 32,
SPNG_FILTER_CHOICE_AVG = 64,
SPNG_FILTER_CHOICE_PAETH = 128,
SPNG_FILTER_CHOICE_ALL = (8|16|32|64|128)
};
enum spng_interlace_method
{
SPNG_INTERLACE_NONE = 0,
SPNG_INTERLACE_ADAM7 = 1
};
/* Channels are always in byte-order */
enum spng_format
{
SPNG_FMT_RGBA8 = 1,
SPNG_FMT_RGBA16 = 2,
SPNG_FMT_RGB8 = 4,
/* Partially implemented, see documentation */
SPNG_FMT_GA8 = 16,
SPNG_FMT_GA16 = 32,
SPNG_FMT_G8 = 64,
/* No conversion or scaling */
SPNG_FMT_PNG = 256,
SPNG_FMT_RAW = 512 /* big-endian (everything else is host-endian) */
};
enum spng_ctx_flags
{
SPNG_CTX_IGNORE_ADLER32 = 1, /* Ignore checksum in DEFLATE streams */
SPNG_CTX_ENCODER = 2 /* Create an encoder context */
};
enum spng_decode_flags
{
SPNG_DECODE_USE_TRNS = 1, /* Deprecated */
SPNG_DECODE_USE_GAMA = 2, /* Deprecated */
SPNG_DECODE_USE_SBIT = 8, /* Undocumented */
SPNG_DECODE_TRNS = 1, /* Apply transparency */
SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */
SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */
};
enum spng_crc_action
{
/* Default for critical chunks */
SPNG_CRC_ERROR = 0,
/* Discard chunk, invalid for critical chunks.
Since v0.6.2: default for ancillary chunks */
SPNG_CRC_DISCARD = 1,
/* Ignore and don't calculate checksum.
Since v0.6.2: also ignores checksums in DEFLATE streams */
SPNG_CRC_USE = 2
};
enum spng_encode_flags
{
SPNG_ENCODE_PROGRESSIVE = 1, /* Initialize for progressive writes */
SPNG_ENCODE_FINALIZE = 2, /* Finalize PNG after encoding image */
};
struct spng_ihdr
{
uint32_t width;
uint32_t height;
uint8_t bit_depth;
uint8_t color_type;
uint8_t compression_method;
uint8_t filter_method;
uint8_t interlace_method;
};
struct spng_plte_entry
{
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha; /* Reserved for internal use */
};
struct spng_plte
{
uint32_t n_entries;
struct spng_plte_entry entries[256];
};
struct spng_trns
{
uint16_t gray;
uint16_t red;
uint16_t green;
uint16_t blue;
uint32_t n_type3_entries;
uint8_t type3_alpha[256];
};
struct spng_chrm_int
{
uint32_t white_point_x;
uint32_t white_point_y;
uint32_t red_x;
uint32_t red_y;
uint32_t green_x;
uint32_t green_y;
uint32_t blue_x;
uint32_t blue_y;
};
struct spng_chrm
{
double white_point_x;
double white_point_y;
double red_x;
double red_y;
double green_x;
double green_y;
double blue_x;
double blue_y;
};
struct spng_iccp
{
char profile_name[80];
size_t profile_len;
char *profile;
};
struct spng_sbit
{
uint8_t grayscale_bits;
uint8_t red_bits;
uint8_t green_bits;
uint8_t blue_bits;
uint8_t alpha_bits;
};
struct spng_text
{
char keyword[80];
int type;
size_t length;
char *text;
uint8_t compression_flag; /* iTXt only */
uint8_t compression_method; /* iTXt, ztXt only */
char *language_tag; /* iTXt only */
char *translated_keyword; /* iTXt only */
};
struct spng_bkgd
{
uint16_t gray; /* Only for gray/gray alpha */
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t plte_index; /* Only for indexed color */
};
struct spng_hist
{
uint16_t frequency[256];
};
struct spng_phys
{
uint32_t ppu_x, ppu_y;
uint8_t unit_specifier;
};
struct spng_splt_entry
{
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t alpha;
uint16_t frequency;
};
struct spng_splt
{
char name[80];
uint8_t sample_depth;
uint32_t n_entries;
struct spng_splt_entry *entries;
};
struct spng_time
{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
};
struct spng_offs
{
int32_t x, y;
uint8_t unit_specifier;
};
struct spng_exif
{
size_t length;
char *data;
};
struct spng_chunk
{
size_t offset;
uint32_t length;
uint8_t type[4];
uint32_t crc;
};
enum spng_location
{
SPNG_AFTER_IHDR = 1,
SPNG_AFTER_PLTE = 2,
SPNG_AFTER_IDAT = 8,
};
struct spng_unknown_chunk
{
uint8_t type[4];
size_t length;
void *data;
enum spng_location location;
};
enum spng_option
{
SPNG_KEEP_UNKNOWN_CHUNKS = 1,
SPNG_IMG_COMPRESSION_LEVEL,
SPNG_IMG_WINDOW_BITS,
SPNG_IMG_MEM_LEVEL,
SPNG_IMG_COMPRESSION_STRATEGY,
SPNG_TEXT_COMPRESSION_LEVEL,
SPNG_TEXT_WINDOW_BITS,
SPNG_TEXT_MEM_LEVEL,
SPNG_TEXT_COMPRESSION_STRATEGY,
SPNG_FILTER_CHOICE,
SPNG_CHUNK_COUNT_LIMIT,
SPNG_ENCODE_TO_BUFFER,
};
typedef void* SPNG_CDECL spng_malloc_fn(size_t size);
typedef void* SPNG_CDECL spng_realloc_fn(void* ptr, size_t size);
typedef void* SPNG_CDECL spng_calloc_fn(size_t count, size_t size);
typedef void SPNG_CDECL spng_free_fn(void* ptr);
struct spng_alloc
{
spng_malloc_fn *malloc_fn;
spng_realloc_fn *realloc_fn;
spng_calloc_fn *calloc_fn;
spng_free_fn *free_fn;
};
struct spng_row_info
{
uint32_t scanline_idx;
uint32_t row_num; /* deinterlaced row index */
int pass;
uint8_t filter;
};
typedef struct spng_ctx spng_ctx;
typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length);
typedef int spng_write_fn(spng_ctx *ctx, void *user, void *src, size_t length);
typedef int spng_rw_fn(spng_ctx *ctx, void *user, void *dst_src, size_t length);
SPNG_API spng_ctx *spng_ctx_new(int flags);
SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags);
SPNG_API void spng_ctx_free(spng_ctx *ctx);
SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size);
SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user);
SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file);
SPNG_API void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error);
SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height);
SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height);
SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size);
SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size);
SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary);
SPNG_API int spng_set_option(spng_ctx *ctx, enum spng_option option, int value);
SPNG_API int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value);
SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len);
/* Decode */
SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags);
/* Progressive decode */
SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len);
SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len);
SPNG_API int spng_decode_chunks(spng_ctx *ctx);
/* Encode/decode */
SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info);
/* Encode */
SPNG_API int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags);
/* Progressive encode */
SPNG_API int spng_encode_scanline(spng_ctx *ctx, const void *scanline, size_t len);
SPNG_API int spng_encode_row(spng_ctx *ctx, const void *row, size_t len);
SPNG_API int spng_encode_chunks(spng_ctx *ctx);
SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte);
SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns);
SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma);
SPNG_API int spng_get_gama_int(spng_ctx *ctx, uint32_t *gama_int);
SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent);
SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text);
SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist);
SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys);
SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt);
SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time);
SPNG_API int spng_get_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t *n_chunks);
/* Official extensions */
SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs);
SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif);
SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte);
SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns);
SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma);
SPNG_API int spng_set_gama_int(spng_ctx *ctx, uint32_t gamma);
SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent);
SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text);
SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist);
SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys);
SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt);
SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time);
SPNG_API int spng_set_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t n_chunks);
/* Official extensions */
SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs);
SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif);
SPNG_API const char *spng_strerror(int err);
SPNG_API const char *spng_version_string(void);
#ifdef __cplusplus
}
#endif
#endif /* SPNG_H */

View File

@ -1,7 +0,0 @@
add_library(lodepng
lodepng/lodepng.cpp
lodepng/lodepng.h
)
create_target_directory_groups(lodepng)
target_include_directories(lodepng INTERFACE lodepng)

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

17
externals/zlib-ng/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,17 @@
set(ZLIB_ENABLE_TESTS OFF)
set(ZLIBNG_ENABLE_TESTS OFF)
set(WITH_GZFILEOP OFF)
set(WITH_GTEST OFF)
set(ZLIB_COMPAT ON)
set(SKIP_INSTALL_ALL ON)
option(BUILD_SHARED_LIBS "Build shared library" OFF)
add_subdirectory(zlib-ng)
# Set ZLIB variables for find_package used by other projects
set(ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/zlib-ng CACHE STRING "Path to zlib include directory")
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "Path to zlib library")
# Setup zlib alias project so FindZLIB doesn't recreate it
add_library(ZLIB::ZLIB ALIAS zlib)

1
externals/zlib-ng/zlib-ng vendored Submodule

View File

@ -122,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

@ -18,15 +18,6 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
// This is important as it will run lint but not abort on error
// Lint has some overly obnoxious "errors" that should really be warnings
abortOnError false
//Uncomment disable lines for test builds...
//disable 'MissingTranslation'bin
//disable 'ExtraTranslation'
}
defaultConfig {
// TODO If this is ever modified, change application_id in strings.xml
@ -100,6 +91,10 @@ android {
path "../../../CMakeLists.txt"
}
}
lint {
abortOnError false
}
namespace 'org.citra.citra_emu'
defaultConfig {
externalNativeBuild {

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.citra.citra_emu">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
@ -39,7 +38,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

@ -355,6 +355,9 @@ public final class SettingsFragmentPresenter {
mView.getActivity().setTitle(R.string.preferences_graphics);
SettingSection rendererSection = mSettings.getSection(Settings.SECTION_RENDERER);
Setting graphicsApi = rendererSection.getSetting(SettingsFile.KEY_GRAPHICS_API);
Setting spvShaderGen = rendererSection.getSetting(SettingsFile.KEY_SPIRV_SHADER_GEN);
Setting asyncShaders = rendererSection.getSetting(SettingsFile.KEY_ASYNC_SHADERS);
Setting resolutionFactor = rendererSection.getSetting(SettingsFile.KEY_RESOLUTION_FACTOR);
Setting filterMode = rendererSection.getSetting(SettingsFile.KEY_FILTER_MODE);
Setting shadersAccurateMul = rendererSection.getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL);
@ -371,6 +374,9 @@ public final class SettingsFragmentPresenter {
//Setting preloadTextures = utilitySection.getSetting(SettingsFile.KEY_PRELOAD_TEXTURES);
sl.add(new HeaderSetting(null, null, R.string.renderer, 0));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_GRAPHICS_API, Settings.SECTION_RENDERER, R.string.graphics_api, 0, R.array.graphicsApiNames, R.array.graphicsApiValues, 2, graphicsApi));
sl.add(new CheckBoxSetting(SettingsFile.KEY_SPIRV_SHADER_GEN, Settings.SECTION_RENDERER, R.string.spirv_shader_gen, R.string.spirv_shader_gen_description, true, spvShaderGen));
sl.add(new CheckBoxSetting(SettingsFile.KEY_ASYNC_SHADERS, Settings.SECTION_RENDERER, R.string.async_shaders, R.string.async_shaders_description, false, asyncShaders));
sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, Settings.SECTION_RENDERER, R.string.internal_resolution, R.string.internal_resolution_description, 1, 4, "x", 1, resolutionFactor));
sl.add(new CheckBoxSetting(SettingsFile.KEY_FILTER_MODE, Settings.SECTION_RENDERER, R.string.linear_filtering, R.string.linear_filtering_description, true, filterMode));
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, Settings.SECTION_RENDERER, R.string.shaders_accurate_mul, R.string.shaders_accurate_mul_description, false, shadersAccurateMul));
@ -412,11 +418,13 @@ public final class SettingsFragmentPresenter {
Setting hardwareRenderer = rendererSection.getSetting(SettingsFile.KEY_HW_RENDERER);
Setting hardwareShader = rendererSection.getSetting(SettingsFile.KEY_HW_SHADER);
Setting vsyncEnable = rendererSection.getSetting(SettingsFile.KEY_USE_VSYNC);
Setting rendererDebug = rendererSection.getSetting(SettingsFile.KEY_RENDERER_DEBUG);
sl.add(new HeaderSetting(null, null, R.string.debug_warning, 0));
sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, Settings.SECTION_CORE, R.string.cpu_jit, R.string.cpu_jit_description, true, useCpuJit, true, mView));
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_RENDERER, Settings.SECTION_RENDERER, R.string.hw_renderer, R.string.hw_renderer_description, true, hardwareRenderer, true, mView));
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, Settings.SECTION_RENDERER, R.string.hw_shaders, R.string.hw_shaders_description, true, hardwareShader, true, mView));
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, Settings.SECTION_RENDERER, R.string.vsync, R.string.vsync_description, true, vsyncEnable));
sl.add(new CheckBoxSetting(SettingsFile.KEY_RENDERER_DEBUG, Settings.SECTION_RENDERER, R.string.renderer_debug, R.string.renderer_debug_description, false, rendererDebug));
}
}

View File

@ -44,6 +44,10 @@ public final class SettingsFile {
public static final String KEY_PREMIUM = "premium";
public static final String KEY_GRAPHICS_API = "graphics_api";
public static final String KEY_SPIRV_SHADER_GEN = "spirv_shader_gen";
public static final String KEY_RENDERER_DEBUG = "renderer_debug";
public static final String KEY_ASYNC_SHADERS = "async_shader_compilation";
public static final String KEY_HW_RENDERER = "use_hw_renderer";
public static final String KEY_HW_SHADER = "use_hw_shader";
public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul";

View File

@ -19,14 +19,16 @@ add_library(citra-android SHARED
default_ini.h
emu_window/emu_window.cpp
emu_window/emu_window.h
emu_window/emu_window_gl.cpp
emu_window/emu_window_gl.h
emu_window/emu_window_vk.cpp
emu_window/emu_window_vk.h
game_info.cpp
game_info.h
game_settings.cpp
game_settings.h
id_cache.cpp
id_cache.h
lodepng_image_interface.cpp
lodepng_image_interface.h
mic.cpp
mic.h
native.cpp
@ -36,6 +38,6 @@ add_library(citra-android SHARED
)
target_link_libraries(citra-android PRIVATE audio_core common core input_common network)
target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics lodepng log mediandk yuv)
target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics log mediandk yuv)
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android)

View File

@ -121,7 +121,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.renderer_debug = sdl2_config->GetBoolean("Renderer", "renderer_debug", false);
Settings::values.async_shader_compilation =
sdl2_config->GetBoolean("Renderer", "async_shader_compilation", true);
Settings::values.spirv_shader_gen =
sdl2_config->GetBoolean("Renderer", "spirv_shader_gen", true);
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 =

View File

@ -98,9 +98,21 @@ use_cpu_jit =
cpu_clock_percentage =
[Renderer]
# Whether to render using GLES or OpenGL
# 0: OpenGL, 1 (default): GLES
use_gles =
# Whether to render using OpenGL or Vulkan
# 1: OpenGL, 2 (default): Vulkan
graphics_api =
# Whether to emit PICA fragment shader using SPIRV or GLSL
# 1: SPIR-V (default), 0: GLSL
spirv_shader_gen =
# Whether to use a worker thread for vulkan command buffer recording
# 1: On (default), 0: Off
async_command_recording =
# Whether to enable additional debugging information during emulation
# 1: On, 0 (default): Off
renderer_debug =
# Whether to use software or hardware rendering.
# 0: Software, 1 (default): Hardware

View File

@ -6,10 +6,7 @@
#include <array>
#include <cstdlib>
#include <string>
#include <android/native_window_jni.h>
#include <glad/glad.h>
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/main.h"
@ -20,52 +17,6 @@
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
static constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT_KHR,
EGL_BLUE_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_RED_SIZE,
8,
EGL_DEPTH_SIZE,
0,
EGL_STENCIL_SIZE,
0,
EGL_NONE};
static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
SharedContext_Android::SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
EGLContext egl_share_context)
: egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config,
egl_empty_attribs.data())},
egl_context{eglCreateContext(egl_display, egl_config, egl_share_context,
egl_context_attribs.data())} {
ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!");
ASSERT_MSG(egl_context, "eglCreateContext() failed!");
}
SharedContext_Android::~SharedContext_Android() {
if (!eglDestroySurface(egl_display, egl_surface)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
if (!eglDestroyContext(egl_display, egl_context)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
}
void SharedContext_Android::MakeCurrent() {
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
}
void SharedContext_Android::DoneCurrent() {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
static bool IsPortraitMode() {
return JNI_FALSE != IDCache::GetEnvForThread()->CallStaticBooleanMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
@ -79,6 +30,10 @@ static void UpdateLandscapeScreenLayout() {
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
render_window = surface;
window_info.type = Frontend::WindowSystemType::Android;
window_info.render_surface = surface;
StopPresenting();
}
@ -98,6 +53,7 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
void EmuWindow_Android::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) {
@ -107,7 +63,7 @@ void EmuWindow_Android::OnFramebufferSizeChanged() {
}
}
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) {
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} {
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
if (!surface) {
@ -115,108 +71,10 @@ EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) {
return;
}
window_width = ANativeWindow_getWidth(surface);
window_height = ANativeWindow_getHeight(surface);
Network::Init();
host_window = surface;
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
return;
}
if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglInitialize() failed");
return;
}
if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1,
&egl_num_configs) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglChooseConfig() failed");
return;
}
CreateWindowSurface();
if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) {
return;
}
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
return;
}
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
egl_context == EGL_NO_CONTEXT) {
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
return;
}
if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) !=
EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed");
return;
}
if (core_context = CreateSharedContext(); !core_context) {
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
return;
}
if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglMakeCurrent() failed");
return;
}
if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) {
LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed");
return;
}
if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) {
LOG_CRITICAL(Frontend, "eglSwapInterval() failed");
return;
}
OnFramebufferSizeChanged();
}
bool EmuWindow_Android::CreateWindowSurface() {
if (!host_window) {
return true;
}
EGLint format{};
eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(host_window, 0, 0, format);
if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0);
egl_surface == EGL_NO_SURFACE) {
return {};
}
return !!egl_surface;
}
void EmuWindow_Android::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::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::~EmuWindow_Android() {
@ -224,34 +82,6 @@ EmuWindow_Android::~EmuWindow_Android() {
DestroyContext();
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android::CreateSharedContext() const {
return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context);
}
void EmuWindow_Android::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::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::PollEvents() {
if (!render_window) {
return;

View File

@ -1,42 +1,17 @@
// Copyright 2019 Citra Emulator Project
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "core/frontend/emu_window.h"
struct ANativeWindow;
class SharedContext_Android : public Frontend::GraphicsContext {
public:
SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
EGLContext egl_share_context);
~SharedContext_Android() override;
void MakeCurrent() override;
void DoneCurrent() override;
private:
EGLDisplay egl_display{};
EGLSurface egl_surface{};
EGLContext egl_context{};
};
class EmuWindow_Android : public Frontend::EmuWindow {
public:
EmuWindow_Android(ANativeWindow* surface);
~EmuWindow_Android();
void Present();
/// Called by the onSurfaceChanges() method to change the surface
void OnSurfaceChanged(ANativeWindow* surface);
@ -47,31 +22,34 @@ public:
void OnTouchMoved(int x, int y);
void PollEvents() override;
void MakeCurrent() override;
void DoneCurrent() override;
void TryPresenting();
void StopPresenting();
virtual void TryPresenting() = 0;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
virtual void StopPresenting() = 0;
private:
protected:
void OnFramebufferSizeChanged();
bool CreateWindowSurface();
void DestroyWindowSurface();
void DestroyContext();
/// Creates the API specific window surface
virtual bool CreateWindowSurface() {}
/// Destroys the API specific window surface
virtual void DestroyWindowSurface() {}
/// Destroys the graphics context
virtual void DestroyContext() {}
protected:
ANativeWindow* render_window{};
ANativeWindow* host_window{};
int window_width{};
int window_height{};
EGLConfig egl_config;
EGLSurface egl_surface{};
EGLContext egl_context{};
EGLDisplay egl_display{};
std::unique_ptr<Frontend::GraphicsContext> core_context;
enum class PresentingState {

View File

@ -0,0 +1,205 @@
// 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 <glad/glad.h>
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window_gl.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 constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT_KHR,
EGL_BLUE_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_RED_SIZE,
8,
EGL_DEPTH_SIZE,
0,
EGL_STENCIL_SIZE,
0,
EGL_NONE};
static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
class SharedContext_Android : public Frontend::GraphicsContext {
public:
SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
EGLContext egl_share_context)
: egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config,
egl_empty_attribs.data())},
egl_context{eglCreateContext(egl_display, egl_config, egl_share_context,
egl_context_attribs.data())} {
ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!");
ASSERT_MSG(egl_context, "eglCreateContext() failed!");
}
~SharedContext_Android() override {
if (!eglDestroySurface(egl_display, egl_surface)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
if (!eglDestroyContext(egl_display, egl_context)) {
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
}
}
void MakeCurrent() {
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
}
void DoneCurrent() {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
private:
EGLDisplay egl_display{};
EGLSurface egl_surface{};
EGLContext egl_context{};
};
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(ANativeWindow* surface)
: EmuWindow_Android{surface} {
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
return;
}
if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglInitialize() failed");
return;
}
if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1,
&egl_num_configs) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglChooseConfig() failed");
return;
}
CreateWindowSurface();
if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) {
return;
}
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
return;
}
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
egl_context == EGL_NO_CONTEXT) {
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
return;
}
if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) !=
EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed");
return;
}
if (core_context = CreateSharedContext(); !core_context) {
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
return;
}
if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) {
LOG_CRITICAL(Frontend, "eglMakeCurrent() failed");
return;
}
if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) {
LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed");
return;
}
if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) {
LOG_CRITICAL(Frontend, "eglSwapInterval() failed");
return;
}
OnFramebufferSizeChanged();
}
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
if (!host_window) {
return true;
}
EGLint format{};
eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(host_window, 0, 0, format);
if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0);
egl_surface == EGL_NO_SURFACE) {
return {};
}
return !!egl_surface;
}
void EmuWindow_Android_OpenGL::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_OpenGL::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;
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_OpenGL::CreateSharedContext() const {
return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context);
}
void EmuWindow_Android_OpenGL::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_OpenGL::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);
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "jni/emu_window/emu_window.h"
struct ANativeWindow;
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
public:
EmuWindow_Android_OpenGL(ANativeWindow* surface);
~EmuWindow_Android_OpenGL() override = default;
void TryPresenting() override;
void StopPresenting() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
private:
bool CreateWindowSurface() override;
void DestroyWindowSurface() override;
void DestroyContext() override;
private:
EGLConfig egl_config;
EGLSurface egl_surface{};
EGLContext egl_context{};
EGLDisplay egl_display{};
};

View File

@ -0,0 +1,65 @@
// 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"
class SharedContext_Android : public Frontend::GraphicsContext {};
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(ANativeWindow* surface)
: EmuWindow_Android{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;
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_Vulkan::CreateSharedContext() const {
return std::make_unique<SharedContext_Android>();
}
void EmuWindow_Android_Vulkan::StopPresenting() {
presenting_state = PresentingState::Stopped;
}
void EmuWindow_Android_Vulkan::TryPresenting() {
if (presenting_state != PresentingState::Running) {
if (presenting_state == PresentingState::Initial) {
presenting_state = PresentingState::Running;
} else {
return;
}
}
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(0);
}
}

View File

@ -0,0 +1,23 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "jni/emu_window/emu_window.h"
struct ANativeWindow;
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
public:
EmuWindow_Android_Vulkan(ANativeWindow* surface);
~EmuWindow_Android_Vulkan() override = default;
void TryPresenting() override;
void StopPresenting() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
private:
bool CreateWindowSurface() override;
};

View File

@ -1,44 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <lodepng.h>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "jni/lodepng_image_interface.h"
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
const std::string& path) {
FileUtil::IOFile file(path, "rb");
size_t read_size = file.GetSize();
std::vector<u8> in(read_size);
if (file.ReadBytes(&in[0], read_size) != read_size) {
LOG_CRITICAL(Frontend, "Failed to decode {}", path);
}
u32 lodepng_ret = lodepng::decode(dst, width, height, in);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
return true;
}
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
u32 width, u32 height) {
std::vector<u8> out;
u32 lodepng_ret = lodepng::encode(out, src, width, height);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
FileUtil::IOFile file(path, "wb");
if (file.WriteBytes(&out[0], out.size()) != out.size()) {
LOG_CRITICAL(Frontend, "Failed to save encode to path={}", path);
return false;
}
return true;
}

View File

@ -1,14 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/image_interface.h"
class LodePNGImageInterface final : public Frontend::ImageInterface {
public:
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) override;
};

View File

@ -25,7 +25,6 @@
#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"
@ -35,17 +34,18 @@
#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_gl.h"
#include "jni/emu_window/emu_window_vk.h"
#include "jni/game_info.h"
#include "jni/game_settings.h"
#include "jni/id_cache.h"
#include "jni/input_manager.h"
#include "jni/lodepng_image_interface.h"
#include "jni/mic.h"
#include "jni/native.h"
#include "jni/ndk_motion.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
#include "video_core/video_core.h"
namespace {
@ -149,7 +149,17 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
return Core::System::ResultStatus::ErrorLoader;
}
window = std::make_unique<EmuWindow_Android>(s_surf);
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
case Settings::GraphicsAPI::OpenGLES:
window = std::make_unique<EmuWindow_Android_OpenGL>(s_surf);
break;
case Settings::GraphicsAPI::Vulkan:
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf);
break;
default:
UNREACHABLE_MSG("Unknown graphics API {}", graphics_api);
}
Core::System& system{Core::System::GetInstance()};
@ -177,9 +187,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>());
system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>());
// Register generic image interface
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
// Register real Mic factory
Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>());
@ -263,6 +270,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
if (window) {
window->OnSurfaceChanged(s_surf);
}
if (VideoCore::g_renderer) {
VideoCore::g_renderer->NotifySurfaceChanged();
}
LOG_INFO(Frontend, "Surface changed");
}

View File

@ -171,4 +171,14 @@
<item>4</item>
<item>5</item>
</integer-array>
<string-array name="graphicsApiNames">
<item>OpenGL</item>
<item>Vulkan</item>
</string-array>
<integer-array name="graphicsApiValues">
<item>1</item>
<item>2</item>
</integer-array>
</resources>

View File

@ -73,6 +73,13 @@
<!-- Graphics settings strings -->
<string name="renderer">Renderer</string>
<string name="vsync">Enable V-Sync</string>
<string name="graphics_api">Graphics API</string>
<string name="spirv_shader_gen">Enable SPIR-V shader generation</string>
<string name="spirv_shader_gen_description">Emits the fragment shader used to emulate PICA using SPIR-V instead of GLSL</string>
<string name="async_shaders">Enable asynchronous shader compilation</string>
<string name="async_shaders_description">Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches</string>
<string name="renderer_debug">Enable debug renderer</string>
<string name="renderer_debug_description">Log additional graphics related debug information. When enabled, game performance will be significantly reduced</string>
<string name="vsync_description">Synchronizes the game frame rate to the refresh rate of your device.</string>
<string name="linear_filtering">Enable linear filtering</string>
<string name="linear_filtering_description">Enables linear filtering, which causes game visuals to appear smoother.</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.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -8,8 +8,6 @@ add_executable(citra
default_ini.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
lodepng_image_interface.cpp
lodepng_image_interface.h
precompiled_headers.h
resource.h
)
@ -17,7 +15,7 @@ add_executable(citra
create_target_directory_groups(citra)
target_link_libraries(citra PRIVATE common core input_common network)
target_link_libraries(citra PRIVATE inih glad lodepng)
target_link_libraries(citra PRIVATE inih glad)
if (MSVC)
target_link_libraries(citra PRIVATE getopt)
endif()

View File

@ -7,37 +7,30 @@
#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"
#include "citra/config.h"
#include "citra/emu_window/emu_window_sdl2.h"
#include "citra/lodepng_image_interface.h"
#include "common/common_paths.h"
#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 "input_common/main.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#undef _UNICODE
#include <getopt.h>
@ -357,9 +350,6 @@ int main(int argc, char** argv) {
// Register frontend applets
Frontend::RegisterDefaultApplets();
// Register generic image interface
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
EmuWindow_SDL2::InitializeSDL2();
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
@ -368,7 +358,7 @@ int main(int argc, char** argv) {
const auto secondary_window =
use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
Frontend::ScopeAcquireContext scope(*emu_window);
const auto scope = emu_window->Acquire();
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);

View File

@ -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__

View File

@ -137,7 +137,9 @@ void EmuWindow_SDL2::Fullscreen() {
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
// Initialize the window
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);
@ -192,7 +194,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(i
}
render_window_id = SDL_GetWindowID(render_window);
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
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());

View File

@ -1,29 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <lodepng.h>
#include "citra/lodepng_image_interface.h"
#include "common/logging/log.h"
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
const std::string& path) {
u32 lodepng_ret = lodepng::decode(dst, width, height, path);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
return true;
}
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
u32 width, u32 height) {
u32 lodepng_ret = lodepng::encode(path, src, width, height);
if (lodepng_ret) {
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
lodepng_error_text(lodepng_ret));
return false;
}
return true;
}

View File

@ -1,14 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/image_interface.h"
class LodePNGImageInterface final : public Frontend::ImageInterface {
public:
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) override;
};

View File

@ -167,8 +167,6 @@ add_executable(citra-qt
precompiled_headers.h
uisettings.cpp
uisettings.h
qt_image_interface.cpp
qt_image_interface.h
updater/updater.cpp
updater/updater.h
updater/updater_p.h
@ -266,9 +264,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 Qt5::Concurrent)
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia Qt5::Concurrent)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (NOT WIN32)
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(citra-qt PRIVATE Qt5::DBus)
endif()

View File

@ -6,10 +6,10 @@
#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"
@ -18,15 +18,21 @@
#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 "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 <objc/message.h>
#include <objc/objc.h>
#endif
#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
EmuThread::~EmuThread() = default;
@ -44,7 +50,7 @@ static GMainWindow* GetMainWindow() {
void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread");
Frontend::ScopeAcquireContext scope(core_context);
const auto scope = core_context.Acquire();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
@ -55,6 +61,7 @@ void EmuThread::run() {
});
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
emit HideLoadingScreen();
core_context.MakeCurrent();
@ -113,88 +120,246 @@ void EmuThread::run() {
#endif
}
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
bool is_secondary)
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
event_handler(event_handler), is_secondary{is_secondary} {
class OpenGLSharedContext : public Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
this->setFormat(format);
format.setVersion(4, 4);
format.setProfile(QSurfaceFormat::CoreProfile);
context->setShareContext(shared_context);
context->setScreen(this->screen());
context->setFormat(format);
context->create();
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
setSurfaceType(QWindow::OpenGLSurface);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
}
OpenGLWindow::~OpenGLWindow() {
context->doneCurrent();
}
void OpenGLWindow::Present() {
if (!isExposed())
return;
context->makeCurrent(this);
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(100, is_secondary);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
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:
/// 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 = share_context->format();
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new.GetValue() : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
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;
}
}
~OpenGLSharedContext() {
context->doneCurrent();
}
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();
if (window) {
#if defined(WIN32)
// Our Win32 Qt external doesn't have the private API.
wsi.render_surface = reinterpret_cast<void*>(window->winId());
#elif defined(__APPLE__)
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
if (wsi.type == Frontend::WindowSystemType::Wayland)
wsi.render_surface = pni->nativeResourceForWindow("surface", window);
else
wsi.render_surface = reinterpret_cast<void*>(window->winId());
#endif
wsi.render_surface_scale = static_cast<float>(window->devicePixelRatio());
} else {
wsi.render_surface = nullptr;
wsi.render_surface_scale = 1.0f;
}
return wsi;
}
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
@ -218,11 +383,11 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_se
GRenderWindow::~GRenderWindow() = default;
void GRenderWindow::MakeCurrent() {
core_context->MakeCurrent();
main_context->MakeCurrent();
}
void GRenderWindow::DoneCurrent() {
core_context->DoneCurrent();
main_context->DoneCurrent();
}
void GRenderWindow::PollEvents() {
@ -393,42 +558,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(),
is_secondary);
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)
if (res_scale == 0) {
res_scale = VideoCore::GetResolutionScaleFactor();
}
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
VideoCore::RequestScreenshot(
VideoCore::g_renderer->RequestScreenshot(
screenshot_image.bits(),
[this, screenshot_path] {
const std::string std_screenshot_path = screenshot_path.toStdString();
@ -445,6 +648,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;
}
@ -456,31 +682,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,26 +113,6 @@ signals:
void HideLoadingScreen();
};
class OpenGLWindow : public QWindow {
Q_OBJECT
public:
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
bool is_secondary = false);
~OpenGLWindow();
void Present();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
private:
std::unique_ptr<QOpenGLContext> context;
QWidget* event_handler;
bool is_secondary;
};
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
Q_OBJECT
@ -185,13 +152,15 @@ public:
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);
@ -211,29 +180,29 @@ 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;

View File

@ -482,6 +482,8 @@ void Config::ReadDebuggingValues() {
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
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) {
@ -625,6 +627,10 @@ void Config::ReadPathValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
ReadGlobalSetting(Settings::values.physical_device);
ReadGlobalSetting(Settings::values.async_shader_compilation);
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__
@ -992,6 +998,8 @@ void Config::SaveDebuggingValues() {
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
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) {
@ -1103,6 +1111,10 @@ void Config::SavePathValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
WriteGlobalSetting(Settings::values.graphics_api);
WriteGlobalSetting(Settings::values.physical_device);
WriteGlobalSetting(Settings::values.async_shader_compilation);
WriteGlobalSetting(Settings::values.spirv_shader_gen);
WriteGlobalSetting(Settings::values.use_hw_renderer);
WriteGlobalSetting(Settings::values.use_hw_shader);
#ifdef __APPLE__

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <QDesktopServices>
#include <QMessageBox>
#include <QUrl>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_debug.h"
@ -12,7 +13,9 @@
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "qcheckbox.h"
#include "ui_configure_debug.h"
#include "video_core/renderer_vulkan/vk_instance.h"
// The QSlider doesn't have an easy way to set a custom step amount,
// so we can just convert from the sliders range (0 - 79) to the expected
@ -35,8 +38,40 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
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);
// Set a minimum width for the label to prevent the slider from changing size.
// This scales across DPIs. (This value should be enough for "xxx%")
@ -62,6 +97,8 @@ void ConfigureDebug::SetConfiguration() {
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());
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.cpu_clock_percentage.UsingGlobal()) {
@ -91,6 +128,8 @@ void ConfigureDebug::ApplyConfiguration() {
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();
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo,

View File

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

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>443</width>
<height>358</height>
<width>523</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
@ -112,11 +112,23 @@
<string>CPU</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<item row="2" column="0">
<widget class="QWidget" name="clock_speed_widget" native="true">
<layout class="QHBoxLayout" name="clock_speed_layout">
<property name="spacing">
<number>7</number>
<number>11</number>
</property>
<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="clock_speed_combo">
@ -180,7 +192,7 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<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>
@ -190,6 +202,20 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text">
<string>Dump command buffers</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="toggle_renderer_debug">
<property name="text">
<string>Enable debug renderer</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -11,17 +11,25 @@
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_graphics.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->hw_renderer_group->isEnabled() &&
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->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);
ui->toggle_async_shaders->setEnabled(not_running);
ui->graphics_api_combo->setCurrentIndex(-1);
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
const bool checked = ui->toggle_hw_renderer->isChecked();
@ -60,17 +68,40 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
// TODO(B3N30): Hide this for macs with none Intel GPUs, too.
ui->toggle_separable_shader->setVisible(false);
#endif
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
SetConfiguration();
}
ConfigureGraphics::~ConfigureGraphics() = default;
void ConfigureGraphics::SetConfiguration() {
if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetHighlight(ui->physical_device_group,
!Settings::values.physical_device.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->physical_device_combo,
&Settings::values.physical_device);
ConfigurationShared::SetHighlight(ui->graphics_api_group,
!Settings::values.graphics_api.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->graphics_api_combo,
&Settings::values.graphics_api);
} else {
ui->physical_device_combo->setCurrentIndex(
static_cast<int>(Settings::values.physical_device.GetValue()));
ui->graphics_api_combo->setCurrentIndex(
static_cast<int>(Settings::values.graphics_api.GetValue()));
}
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->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
@ -90,6 +121,14 @@ void ConfigureGraphics::ApplyConfiguration() {
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_shader_compilation,
ui->toggle_async_shaders, async_shader_compilation);
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();
@ -110,11 +149,23 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->toggle_disk_shader_cache->setEnabled(
Settings::values.use_disk_shader_cache.UsingGlobal());
ui->toggle_vsync_new->setEnabled(Settings::values.use_vsync_new.UsingGlobal());
ui->toggle_async_shaders->setEnabled(
Settings::values.async_shader_compilation.UsingGlobal());
ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal());
ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal());
return;
}
ui->toggle_shader_jit->setVisible(false);
ConfigurationShared::SetColoredComboBox(
ui->graphics_api_combo, ui->graphics_api_group,
static_cast<u32>(Settings::values.graphics_api.GetValue(true)));
ConfigurationShared::SetColoredComboBox(
ui->physical_device_combo, ui->physical_device_group,
static_cast<u32>(Settings::values.physical_device.GetValue(true)));
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,
@ -128,4 +179,48 @@ void ConfigureGraphics::SetupPerGameUI() {
use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new,
use_vsync_new);
ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders,
Settings::values.async_shader_compilation,
async_shader_compilation);
ConfigurationShared::SetColoredTristate(ui->spirv_shader_gen, Settings::values.spirv_shader_gen,
spirv_shader_gen);
}
void ConfigureGraphics::DiscoverPhysicalDevices() {
if (physical_devices_discovered) {
return;
}
Vulkan::Instance instance{};
const auto physical_devices = instance.GetPhysicalDevices();
for (const vk::PhysicalDevice& physical_device : physical_devices) {
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
ui->physical_device_combo->addItem(name);
}
physical_devices_discovered = true;
}
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
bool is_visible{false};
// When configuring per-game the physical device combo should be
// shown either when the global api is used and that is vulkan or
// vulkan is set as the per-game api.
if (!Settings::IsConfiguringGlobal()) {
const auto global_graphics_api = Settings::values.graphics_api.GetValue(true);
const bool using_global = index == 0;
if (!using_global) {
index -= ConfigurationShared::USE_GLOBAL_OFFSET;
}
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
is_visible = (using_global && global_graphics_api == Settings::GraphicsAPI::Vulkan) ||
graphics_api == Settings::GraphicsAPI::Vulkan;
} else {
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
}
ui->physical_device_group->setVisible(is_visible);
ui->spirv_shader_gen->setVisible(is_visible);
}

View File

@ -30,12 +30,19 @@ public:
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_shader_compilation;
ConfigurationShared::CheckState spirv_shader_gen;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
bool physical_devices_discovered = false;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>430</height>
<width>454</width>
<height>579</height>
</rect>
</property>
<property name="minimumSize">
@ -20,6 +20,97 @@
<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>
<widget class="QWidget" name="graphics_api_group" native="true">
<layout class="QHBoxLayout" name="graphics_api_group_2">
<property name="spacing">
<number>7</number>
</property>
<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="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>
</widget>
</item>
<item>
<widget class="QWidget" name="physical_device_group" native="true">
<layout class="QHBoxLayout" name="physical_device_group_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="physical_device_label">
<property name="text">
<string>Physical device</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="physical_device_combo"/>
</item>
</layout>
</widget>
</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 +209,13 @@
<string>Advanced</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_async_shaders">
<property name="text">
<string>Async Shader Compilation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_disk_shader_cache">
<property name="toolTip">

View File

@ -16,7 +16,6 @@
#include "citra_qt/util/spinbox.h"
#include "common/color.h"
#include "core/core.h"
#include "core/hw/gpu.h"
#include "core/memory.h"
#include "video_core/pica_state.h"
#include "video_core/regs_framebuffer.h"

View File

@ -90,4 +90,4 @@ bool CheckAuthorizationForMicrophone() {
return authorized_microphone;
}
} // AppleAuthorization
} // namespace AppleAuthorization

View File

@ -18,17 +18,6 @@
#include <QtGui>
#include <QtWidgets>
#include <fmt/format.h>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __unix__
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
#endif
#include "citra_qt/aboutdialog.h"
#include "citra_qt/applets/mii_selector.h"
#include "citra_qt/applets/swkbd.h"
@ -61,7 +50,6 @@
#include "citra_qt/movie/movie_play_dialog.h"
#include "citra_qt/movie/movie_record_dialog.h"
#include "citra_qt/multiplayer/state.h"
#include "citra_qt/qt_image_interface.h"
#include "citra_qt/uisettings.h"
#include "citra_qt/updater/updater.h"
#include "citra_qt/util/clickable_label.h"
@ -71,9 +59,7 @@
#include "common/file_util.h"
#include "common/literals.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/memory_detect.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
@ -88,8 +74,6 @@
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/nfc/nfc.h"
@ -100,9 +84,20 @@
#include "input_common/main.h"
#include "network/network_settings.h"
#include "ui_main.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __unix__
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
#endif
#ifdef USE_DISCORD_PRESENCE
#include "citra_qt/discord_impl.h"
#endif
@ -356,6 +351,22 @@ void GMainWindow::InitializeWidgets() {
statusBar()->addPermanentWidget(label);
}
// Setup Graphics API button
graphics_api_button = new QPushButton();
graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton"));
graphics_api_button->setFocusPolicy(Qt::NoFocus);
UpdateAPIIndicator(false);
connect(graphics_api_button, &QPushButton::clicked, this, [this] {
if (emulation_running) {
return;
}
UpdateAPIIndicator(true);
});
statusBar()->insertPermanentWidget(0, graphics_api_button);
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
@ -579,6 +590,8 @@ void GMainWindow::InitializeHotkeys() {
});
connect_shortcut(QStringLiteral("Toggle Texture Dumping"),
[&] { Settings::values.dump_textures = !Settings::values.dump_textures; });
connect_shortcut(QStringLiteral("Toggle Custom Textures"),
[&] { Settings::values.custom_textures = !Settings::values.custom_textures; });
// We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
// the variable hold a garbage value after this function exits
static constexpr u16 SPEED_LIMIT_STEP = 5;
@ -1026,16 +1039,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
render_window->InitRenderTarget();
secondary_window->InitRenderTarget();
Frontend::ScopeAcquireContext scope(*render_window);
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
"have the latest graphics driver.");
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
return false;
}
const auto scope = render_window->Acquire();
Core::System& system{Core::System::GetInstance()};
@ -1094,7 +1098,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorVideoCore:
QMessageBox::critical(
this, tr("Video Core Error"),
tr("An error has occurred. Please <a "
tr("An error has occurred during intialization of the video backend. Please <a "
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
"the "
"log</a> for more details. "
@ -1109,10 +1113,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
"proper drivers for your graphics card from the manufacturer's website."));
break;
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
break;
default:
QMessageBox::critical(
this, tr("Error while loading ROM!"),
@ -1170,6 +1170,9 @@ void GMainWindow::BootGame(const QString& filename) {
LOG_INFO(Frontend, "Using per game config file for title id {}", config_file_name);
Settings::LogSettings();
// Update API indicator to reflect the per-game graphics API, if set.
UpdateAPIIndicator(false);
}
// Save configurations
@ -1255,10 +1258,6 @@ void GMainWindow::BootGame(const QString& filename) {
setMouseTracking(true);
}
// show and hide the render_window to create the context
render_window->show();
render_window->hide();
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
loading_screen->show();
@ -1808,6 +1807,9 @@ void GMainWindow::OnPauseContinueGame() {
void GMainWindow::OnStopGame() {
ShutdownGame();
Settings::RestoreGlobalState(false);
// If a per-game graphics API was set we should reset to the global option
UpdateAPIIndicator(false);
}
void GMainWindow::OnLoadComplete() {
@ -2030,6 +2032,7 @@ void GMainWindow::OnConfigure() {
setMouseTracking(false);
}
UpdateSecondaryWindowVisibility();
UpdateAPIIndicator(false);
UpdateBootHomeMenuState();
} else {
Settings::values.input_profiles = old_input_profiles;
@ -2352,6 +2355,26 @@ void GMainWindow::ShowMouseCursor() {
}
}
void GMainWindow::UpdateAPIIndicator(bool override) {
static std::array graphics_apis = {QStringLiteral("OPENGL"), QStringLiteral("OPENGLES"),
QStringLiteral("VULKAN")};
static std::array graphics_api_colors = {QStringLiteral("#00ccdd"), QStringLiteral("#ba2a8d"),
QStringLiteral("#91242a")};
u32 api_index = static_cast<u32>(Settings::values.graphics_api.GetValue());
if (override) {
api_index = (api_index + 1) % graphics_apis.size();
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
}
const QString style_sheet = QStringLiteral("QPushButton { font-weight: bold; color: %0; }")
.arg(graphics_api_colors[api_index]);
graphics_api_button->setText(graphics_apis[api_index]);
graphics_api_button->setStyleSheet(style_sheet);
}
void GMainWindow::OnMouseActivity() {
ShowMouseCursor();
}
@ -2786,14 +2809,6 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setSwapInterval(0);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
QSurfaceFormat::setDefaultFormat(format);
SetHighDPIAttributes();
#ifdef __APPLE__
@ -2826,9 +2841,6 @@ int main(int argc, char* argv[]) {
system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
// Register Qt image interface
system.RegisterImageInterface(std::make_shared<QtImageInterface>());
main_window.show();
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,

View File

@ -7,6 +7,7 @@
#include <array>
#include <memory>
#include <QMainWindow>
#include <QPushButton>
#include <QTimer>
#include <QTranslator>
#include "citra_qt/compatibility_list.h"
@ -252,6 +253,7 @@ private:
void HideMouseCursor();
void ShowMouseCursor();
void OpenPerGameConfiguration(u64 title_id, const QString& file_name);
void UpdateAPIIndicator(bool override);
std::unique_ptr<Ui::MainWindow> ui;
@ -267,6 +269,7 @@ private:
QLabel* emu_speed_label = nullptr;
QLabel* game_fps_label = nullptr;
QLabel* emu_frametime_label = nullptr;
QPushButton* graphics_api_button = nullptr;
QTimer status_bar_update_timer;
bool message_label_used_for_movie = false;

View File

@ -1,38 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QImage>
#include <QString>
#include "citra_qt/qt_image_interface.h"
#include "common/logging/log.h"
bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
const std::string& path) {
QImage image(QString::fromStdString(path));
if (image.isNull()) {
LOG_ERROR(Frontend, "Failed to open {} for decoding", path);
return false;
}
width = image.width();
height = image.height();
image = image.convertToFormat(QImage::Format_RGBA8888);
// Write RGBA8 to vector
dst = std::vector<u8>(image.constBits(), image.constBits() + (width * height * 4));
return true;
}
bool QtImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) {
QImage image(src.data(), width, height, QImage::Format_RGBA8888);
if (!image.save(QString::fromStdString(path), "PNG")) {
LOG_ERROR(Frontend, "Failed to save {}", path);
return false;
}
return true;
}

View File

@ -1,14 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/frontend/image_interface.h"
class QtImageInterface final : public Frontend::ImageInterface {
public:
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
u32 height) override;
};

View File

@ -63,11 +63,13 @@ add_library(common STATIC
arch.h
archives.h
assert.h
async_handle.h
atomic_ops.h
detached_tasks.cpp
detached_tasks.h
bit_field.h
bit_set.h
bit_util.h
cityhash.cpp
cityhash.h
color.h
@ -76,9 +78,14 @@ add_library(common STATIC
common_precompiled_headers.h
common_types.h
construct.h
dds-ktx.h
error.cpp
error.h
file_util.cpp
file_util.h
hash.h
image_util.cpp
image_util.h
linear_disk_cache.h
literals.h
logging/backend.cpp
@ -100,12 +107,14 @@ add_library(common STATIC
misc.cpp
param_package.cpp
param_package.h
polyfill_thread.h
precompiled_headers.h
quaternion.h
ring_buffer.h
scm_rev.cpp
scm_rev.h
scope_exit.h
scratch_buffer.h
settings.cpp
settings.h
serialization/atomic.h
@ -124,9 +133,11 @@ add_library(common STATIC
thread.cpp
thread.h
thread_queue_list.h
thread_worker.h
threadsafe_queue.h
timer.cpp
timer.h
unique_function.h
vector_math.h
web_result.h
x64/cpu_detect.cpp
@ -140,7 +151,7 @@ add_library(common STATIC
create_target_directory_groups(common)
target_link_libraries(common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization Boost::iostreams)
target_link_libraries(common PRIVATE libzstd_static)
target_link_libraries(common PRIVATE libzstd_static spng::spng)
set_target_properties(common PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
if ("x86_64" IN_LIST ARCHITECTURE)

39
src/common/async_handle.h Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <type_traits>
namespace Common {
struct AsyncHandle {
public:
AsyncHandle(bool is_done_ = false) : is_done{is_done_} {}
[[nodiscard]] bool IsDone() noexcept {
return is_done.load(std::memory_order::relaxed);
}
void WaitDone() noexcept {
std::unique_lock lock{mutex};
condvar.wait(lock, [this] { return is_done.load(std::memory_order::relaxed); });
}
void MarkDone(bool done = true) noexcept {
std::scoped_lock lock{mutex};
is_done = done;
condvar.notify_all();
}
private:
std::condition_variable condvar;
std::mutex mutex;
std::atomic_bool is_done{false};
};
} // namespace Common

66
src/common/bit_util.h Normal file
View File

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <bit>
#include <climits>
#include <cstddef>
#include <type_traits>
#include "common/common_types.h"
namespace Common {
/// Gets the size of a specified type T in bits.
template <typename T>
[[nodiscard]] constexpr std::size_t BitSize() {
return sizeof(T) * CHAR_BIT;
}
[[nodiscard]] constexpr u32 MostSignificantBit32(const u32 value) {
return 31U - static_cast<u32>(std::countl_zero(value));
}
[[nodiscard]] constexpr u32 MostSignificantBit64(const u64 value) {
return 63U - static_cast<u32>(std::countl_zero(value));
}
[[nodiscard]] constexpr u32 Log2Floor32(const u32 value) {
return MostSignificantBit32(value);
}
[[nodiscard]] constexpr u32 Log2Floor64(const u64 value) {
return MostSignificantBit64(value);
}
[[nodiscard]] constexpr u32 Log2Ceil32(const u32 value) {
const u32 log2_f = Log2Floor32(value);
return log2_f + static_cast<u32>((value ^ (1U << log2_f)) != 0U);
}
[[nodiscard]] constexpr u32 Log2Ceil64(const u64 value) {
const u64 log2_f = Log2Floor64(value);
return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
}
template <typename T>
requires std::is_unsigned_v<T>
[[nodiscard]] constexpr bool IsPow2(T value) {
return value != 0 && (value & (value - 1)) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] T NextPow2(T value) {
return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
}
template <size_t bit_index, typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Bit(const T value) {
static_assert(bit_index < BitSize<T>(), "bit_index must be smaller than size of T");
return ((value >> bit_index) & T(1)) == T(1);
}
} // namespace Common

View File

@ -52,6 +52,11 @@ namespace Common::Color {
return value >> 2;
}
/// Averages the RGB components of a color
[[nodiscard]] constexpr u8 AverageRgbComponents(const Common::Vec4<u8>& color) {
return (static_cast<u32>(color.r()) + color.g() + color.b()) / 3;
}
/**
* Decode a color stored in RGBA8 format
* @param bytes Pointer to encoded source color
@ -115,6 +120,44 @@ namespace Common::Color {
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
}
/**
* Decode a color stored in IA8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeIA8(const u8* bytes) {
return {bytes[1], bytes[1], bytes[1], bytes[0]};
}
/**
* Decode a color stored in I8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeI8(const u8* bytes) {
return {bytes[0], bytes[0], bytes[0], 255};
}
/**
* Decode a color stored in A8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeA8(const u8* bytes) {
return {0, 0, 0, bytes[0]};
}
/**
* Decode a color stored in IA4 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeIA4(const u8* bytes) {
u8 i = Common::Color::Convert4To8((bytes[0] & 0xF0) >> 4);
u8 a = Common::Color::Convert4To8(bytes[0] & 0x0F);
return {i, i, i, a};
}
/**
* Decode a depth value stored in D16 format
* @param bytes Pointer to encoded source value
@ -176,6 +219,7 @@ inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[1] = color.r();
bytes[0] = color.g();
}
/**
* Encode a color as RGB565 format
* @param color Source color to encode
@ -212,6 +256,43 @@ inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
std::memcpy(bytes, &data, sizeof(data));
}
/**
* Encode a color as IA8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeIA8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[1] = AverageRgbComponents(color);
bytes[0] = color.a();
}
/**
* Encode a color as I8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeI8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = AverageRgbComponents(color);
}
/**
* Encode a color as A8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeA8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = color.a();
}
/**
* Encode a color as IA4 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeIA4(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = (Convert8To4(AverageRgbComponents(color)) << 4) | Convert8To4(color.a());
}
/**
* Encode a 16 bit depth value as D16 format
* @param value 16 bit source depth value to encode

View File

@ -55,6 +55,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
#endif // _MSC_VER
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
a = a & b; \
return a; \
} \
constexpr type& operator^=(type& a, type b) noexcept { \
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.

1465
src/common/dds-ktx.h Normal file

File diff suppressed because it is too large Load Diff

57
src/common/error.cpp Normal file
View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#ifdef _WIN32
#include <windows.h>
#else
#include <cerrno>
#include <cstring>
#endif
#include "common/error.h"
namespace Common {
std::string NativeErrorToString(int e) {
#ifdef _WIN32
LPSTR err_str;
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
if (!res) {
return "(FormatMessageA failed to format error)";
}
std::string ret(err_str);
LocalFree(err_str);
return ret;
#else
char err_str[255];
#if (defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))) || \
defined(ANDROID)
// Thread safe (GNU-specific)
const char* str = strerror_r(e, err_str, sizeof(err_str));
return std::string(str);
#else
// Thread safe (XSI-compliant)
int second_err = strerror_r(e, err_str, sizeof(err_str));
if (second_err != 0) {
return "(strerror_r failed to format error)";
}
return std::string(err_str);
#endif // GLIBC etc.
#endif // _WIN32
}
std::string GetLastErrorMsg() {
#ifdef _WIN32
return NativeErrorToString(GetLastError());
#else
return NativeErrorToString(errno);
#endif
}
} // namespace Common

21
src/common/error.h Normal file
View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common {
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in error.cpp.
[[nodiscard]] std::string GetLastErrorMsg();
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in error.cpp.
[[nodiscard]] std::string NativeErrorToString(int e);
} // namespace Common

View File

@ -348,6 +348,9 @@ public:
[[nodiscard]] explicit operator bool() const {
return IsGood();
}
[[nodiscard]] std::FILE* Handle() {
return m_file;
}
bool Seek(s64 off, int origin);
[[nodiscard]] u64 Tell() const;

View File

@ -4,6 +4,7 @@
#pragma once
#include <concepts>
#include <cstddef>
#include <cstring>
#include "common/cityhash.h"
@ -37,10 +38,17 @@ static inline u64 ComputeStructHash64(const T& data) noexcept {
* Combines the seed parameter with the provided hash, producing a new unique hash
* Implementation from: http://boost.sourceforge.net/doc/html/boost/hash_combine.html
*/
inline u64 HashCombine(std::size_t& seed, const u64 hash) {
inline u64 HashCombine(std::size_t seed, const u64 hash) {
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename T>
struct IdentityHash {
T operator()(const T& value) const {
return value;
}
};
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
template <typename T>
struct HashableStruct {

165
src/common/image_util.cpp Normal file
View File

@ -0,0 +1,165 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <spng.h>
#define DDSKTX_IMPLEMENT
#include "common/file_util.h"
#include "common/image_util.h"
#include "common/logging/log.h"
namespace Common {
namespace {
void spng_free(spng_ctx* ctx) {
if (ctx) {
spng_ctx_free(ctx);
}
}
auto make_spng_ctx(int flags) {
return std::unique_ptr<spng_ctx, decltype(&spng_free)>(spng_ctx_new(flags), spng_free);
}
} // Anonymous namespace
bool ParsePNG(std::span<const u8> png_data, size_t& decoded_size, u32& width, u32& height) {
auto ctx = make_spng_ctx(0);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
return false;
}
spng_ihdr ihdr{};
if (spng_get_ihdr(ctx.get(), &ihdr)) {
return false;
}
width = ihdr.width;
height = ihdr.height;
const int format = SPNG_FMT_RGBA8;
if (spng_decoded_image_size(ctx.get(), format, &decoded_size)) {
return false;
}
return true;
}
bool DecodePNG(std::span<const u8> png_data, std::span<u8> out_data) {
auto ctx = make_spng_ctx(0);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
return false;
}
const int format = SPNG_FMT_RGBA8;
size_t decoded_len = 0;
if (spng_decoded_image_size(ctx.get(), format, &decoded_len)) {
return false;
}
if (spng_decode_image(ctx.get(), out_data.data(), decoded_len, format, 0)) {
return false;
}
return true;
}
bool ParseDDSKTX(std::span<const u8> dds_data, size_t& decoded_size, u32& width, u32& height,
ddsktx_format& format) {
ddsktx_texture_info tc{};
const int size = static_cast<int>(dds_data.size());
if (!ddsktx_parse(&tc, dds_data.data(), size, nullptr)) {
return false;
}
width = tc.width;
height = tc.height;
format = tc.format;
ddsktx_sub_data sub_data{};
ddsktx_get_sub(&tc, &sub_data, dds_data.data(), size, 0, 0, 0);
decoded_size = sub_data.size_bytes;
return true;
}
bool LoadDDSKTX(std::span<const u8> dds_data, std::span<u8> out_data) {
ddsktx_texture_info tc{};
const int size = static_cast<int>(dds_data.size());
if (!ddsktx_parse(&tc, dds_data.data(), size, nullptr)) {
return false;
}
ddsktx_sub_data sub_data{};
ddsktx_get_sub(&tc, &sub_data, dds_data.data(), size, 0, 0, 0);
std::memcpy(out_data.data(), sub_data.buff, sub_data.size_bytes);
return true;
}
bool EncodePNG(const std::string& out_path, std::span<u8> in_data, u32 width, u32 height,
s32 level) {
auto ctx = make_spng_ctx(SPNG_CTX_ENCODER);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level)) {
return false;
}
if (spng_set_option(ctx.get(), SPNG_ENCODE_TO_BUFFER, 1)) {
return false;
}
spng_ihdr ihdr{};
ihdr.width = width;
ihdr.height = height;
ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
ihdr.bit_depth = 8;
if (spng_set_ihdr(ctx.get(), &ihdr)) {
return false;
}
if (spng_encode_image(ctx.get(), in_data.data(), in_data.size(), SPNG_FMT_PNG,
SPNG_ENCODE_FINALIZE)) {
return false;
}
int ret{};
size_t png_size{};
u8* png_buf = reinterpret_cast<u8*>(spng_get_png_buffer(ctx.get(), &png_size, &ret));
if (!png_buf) {
return false;
}
auto file = FileUtil::IOFile(out_path, "wb");
file.WriteBytes(png_buf, png_size);
size_t image_len = 0;
spng_decoded_image_size(ctx.get(), SPNG_FMT_PNG, &image_len);
LOG_ERROR(Common, "{} byte {} by {} image saved to {} at level {}", image_len, width, height,
out_path, level);
return true;
}
void FlipTexture(std::span<u8> in_data, u32 width, u32 height, u32 stride) {
for (u32 line = 0; line < height / 2; line++) {
const u32 offset_1 = line * stride;
const u32 offset_2 = (height - line - 1) * stride;
// Swap lines
std::swap_ranges(in_data.begin() + offset_1, in_data.begin() + offset_1 + stride,
in_data.begin() + offset_2);
}
}
} // namespace Common

26
src/common/image_util.h Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <span>
#include <vector>
#include "common/common_types.h"
#include "common/dds-ktx.h"
namespace Common {
bool ParsePNG(std::span<const u8> png_data, size_t& decoded_size, u32& width, u32& height);
bool DecodePNG(std::span<const u8> png_data, std::span<u8> out_data);
bool ParseDDSKTX(std::span<const u8> dds_data, size_t& decoded_size, u32& width, u32& height,
ddsktx_format& format);
bool LoadDDSKTX(std::span<const u8> dds_data, std::span<u8> out_data);
bool EncodePNG(const std::string& out_path, std::span<u8> in_data, u32 width, u32 height,
s32 level = 6);
void FlipTexture(std::span<u8> in_data, u32 width, u32 height, u32 stride);
} // namespace Common

View File

@ -236,6 +236,7 @@ void DebuggerBackend::Write(const Entry& entry) {
CLS(Render) \
SUB(Render, Software) \
SUB(Render, OpenGL) \
SUB(Render, Vulkan) \
CLS(Audio) \
SUB(Audio, DSP) \
SUB(Audio, Sink) \

View File

@ -8,6 +8,7 @@
#include <array>
#include "common/common_types.h"
#include "common/logging/formatter.h"
namespace Log {
// trims up to and including the last of ../, ..\, src/, src\ in a string
@ -103,6 +104,7 @@ enum class Class : ClassType {
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
Render_Vulkan, ///< Vulkan backend
Audio, ///< Audio emulation
Audio_DSP, ///< The HLE and LLE implementations of the DSP
Audio_Sink, ///< Emulator audio output backend

View File

@ -4,6 +4,7 @@
#pragma once
#include <compare>
#include <cstdlib>
#include <type_traits>
@ -23,19 +24,41 @@ struct Rectangle {
constexpr Rectangle(T left, T top, T right, T bottom)
: left(left), top(top), right(right), bottom(bottom) {}
[[nodiscard]] T GetWidth() const {
constexpr void operator*=(const T value) {
left *= value;
top *= value;
right *= value;
bottom *= value;
}
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
(bottom == rhs.bottom);
}
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
return !operator==(rhs);
}
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
return Rectangle{left * value, top * value, right * value, bottom * value};
}
[[nodiscard]] constexpr Rectangle<T> operator/(const T value) const {
return Rectangle{left / value, top / value, right / value, bottom / value};
}
[[nodiscard]] constexpr T GetWidth() const {
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
}
[[nodiscard]] T GetHeight() const {
[[nodiscard]] constexpr T GetHeight() const {
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
}
[[nodiscard]] Rectangle<T> TranslateX(const T x) const {
[[nodiscard]] constexpr Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};
}
[[nodiscard]] Rectangle<T> TranslateY(const T y) const {
[[nodiscard]] constexpr Rectangle<T> TranslateY(const T y) const {
return Rectangle{left, top + y, right, bottom + y};
}
[[nodiscard]] Rectangle<T> Scale(const float s) const {
[[nodiscard]] constexpr Rectangle<T> Scale(const float s) const {
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
static_cast<T>(top + GetHeight() * s)};
}

View File

@ -3,8 +3,8 @@
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <span>
#include <vector>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
@ -79,6 +79,7 @@ public:
: backing_mem(std::move(backing_mem_)), offset(0) {
Init();
}
MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
: backing_mem(std::move(backing_mem_)), offset(offset_) {
ASSERT(offset <= backing_mem->GetSize());
@ -93,11 +94,11 @@ public:
return cptr;
}
u8* GetPtr() {
operator const u8*() const {
return cptr;
}
operator const u8*() const {
u8* GetPtr() {
return cptr;
}
@ -105,6 +106,14 @@ public:
return cptr;
}
auto GetWriteBytes(std::size_t size) {
return std::span{cptr, size > csize ? csize : size};
}
auto GetReadBytes(std::size_t size) const {
return std::span{cptr, size > csize ? csize : size};
}
std::size_t GetSize() const {
return csize;
}

View File

@ -0,0 +1,337 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
//
// TODO: remove this file when jthread is supported by all compilation targets
//
#pragma once
#include <version>
#ifdef __cpp_lib_jthread
#include <stop_token>
#include <thread>
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
cv.wait(lock, token, std::move(pred));
}
} // namespace Common
#else
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
namespace std {
namespace polyfill {
using stop_state_callback = size_t;
class stop_state {
public:
stop_state() = default;
~stop_state() = default;
bool request_stop() {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Already set, nothing to do.
return false;
}
// Mark stop requested.
m_stop_requested = true;
while (!m_callbacks.empty()) {
// Get an iterator to the first element.
const auto it = m_callbacks.begin();
// Move the callback function out of the map.
function<void()> f;
swap(it->second, f);
// Erase the now-empty map element.
m_callbacks.erase(it);
// Run the callback.
if (f) {
f();
}
}
return true;
}
bool stop_requested() const {
unique_lock lk{m_lock};
return m_stop_requested;
}
stop_state_callback insert_callback(function<void()> f) {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Stop already requested. Don't insert anything,
// just run the callback synchronously.
if (f) {
f();
}
return 0;
}
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
m_callbacks.emplace(ret, move(f));
return ret;
}
void remove_callback(stop_state_callback cb) {
unique_lock lk{m_lock};
m_callbacks.erase(cb);
}
private:
mutable recursive_mutex m_lock;
map<stop_state_callback, function<void()>> m_callbacks;
stop_state_callback m_next_callback{0};
bool m_stop_requested{false};
};
} // namespace polyfill
#ifndef __cpp_lib_concepts
template <class T, class... Args>
concept constructible_from = is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>;
#endif
class stop_token;
class stop_source;
struct nostopstate_t {
explicit nostopstate_t() = default;
};
inline constexpr nostopstate_t nostopstate{};
template <class Callback>
class stop_callback;
class stop_token {
public:
stop_token() noexcept = default;
stop_token(const stop_token&) noexcept = default;
stop_token(stop_token&&) noexcept = default;
stop_token& operator=(const stop_token&) noexcept = default;
stop_token& operator=(stop_token&&) noexcept = default;
~stop_token() = default;
void swap(stop_token& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source {
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
explicit stop_source(nostopstate_t) noexcept {}
stop_source(const stop_source&) noexcept = default;
stop_source(stop_source&&) noexcept = default;
stop_source& operator=(const stop_source&) noexcept = default;
stop_source& operator=(stop_source&&) noexcept = default;
~stop_source() = default;
void swap(stop_source& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] stop_token get_token() const noexcept {
return stop_token(m_stop_state);
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
bool request_stop() noexcept {
return m_stop_state && m_stop_state->request_stop();
}
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
: m_stop_state(move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback {
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(const stop_token& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(move(st.m_stop_state)) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(move(cb));
}
}
~stop_callback() {
if (m_stop_state && m_callback) {
m_stop_state->remove_callback(m_callback);
}
}
stop_callback(const stop_callback&) = delete;
stop_callback(stop_callback&&) = delete;
stop_callback& operator=(const stop_callback&) = delete;
stop_callback& operator=(stop_callback&&) = delete;
private:
shared_ptr<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread {
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args,
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(move(f), move(args)...)) {}
~jthread() {
if (joinable()) {
request_stop();
join();
}
}
jthread(const jthread&) = delete;
jthread(jthread&&) noexcept = default;
jthread& operator=(const jthread&) = delete;
jthread& operator=(jthread&& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
return *this;
}
void swap(jthread& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool joinable() const noexcept {
return m_thread.joinable();
}
void join() {
m_thread.join();
}
void detach() {
m_thread.detach();
m_stop_state.reset();
}
[[nodiscard]] id get_id() const noexcept {
return m_thread.get_id();
}
[[nodiscard]] native_handle_type native_handle() {
return m_thread.native_handle();
}
[[nodiscard]] stop_source get_stop_source() noexcept {
return stop_source(m_stop_state);
}
[[nodiscard]] stop_token get_stop_token() const noexcept {
return stop_source(m_stop_state).get_token();
}
bool request_stop() noexcept {
return get_stop_source().request_stop();
}
[[nodiscard]] static unsigned int hardware_concurrency() noexcept {
return thread::hardware_concurrency();
}
private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(move(f), get_stop_token(), move(args)...);
} else {
return thread(move(f), move(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
std::stop_callback callback(token, [&] { cv.notify_all(); });
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
}
} // namespace Common
#endif

View File

@ -0,0 +1,47 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <span>
#include <type_traits>
namespace Common {
/**
* A ScratchBuffer is a simple heap allocated array without member initialization.
* Main usage is for temporary buffers passed to threads for example
*/
template <typename T>
class ScratchBuffer {
static_assert(std::is_trivial_v<T>, "Must use a POD type");
public:
ScratchBuffer(std::size_t size_) : size{size_} {
buffer = std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]);
}
[[nodiscard]] std::size_t Size() const noexcept {
return size;
}
[[nodiscard]] T* Data() const noexcept {
return buffer.get();
}
[[nodiscard]] std::span<const T> Span(u32 index = 0) const noexcept {
return std::span<const T>{buffer.get() + index, size - index};
}
[[nodiscard]] std::span<T> Span(u32 index = 0) noexcept {
return std::span<T>{buffer.get() + index, size - index};
}
private:
std::unique_ptr<T> buffer;
std::size_t size;
};
} // namespace Common

View File

@ -8,7 +8,6 @@
#include "common/settings.h"
#include "core/core.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/service/cam/cam.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/ir_rst.h"
@ -20,6 +19,17 @@
namespace Settings {
std::string_view GetAPIName(GraphicsAPI api) {
switch (api) {
case GraphicsAPI::OpenGL:
return "OpenGL";
case GraphicsAPI::OpenGLES:
return "OpenGLES";
case GraphicsAPI::Vulkan:
return "Vulkan";
}
}
std::string_view GetAudioEmulationName(AudioEmulation emulation) {
switch (emulation) {
case AudioEmulation::HLE:
@ -41,9 +51,8 @@ void Apply() {
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer.GetValue();
VideoCore::g_shader_jit_enabled = values.use_shader_jit.GetValue();
VideoCore::g_hw_shader_enabled = values.use_hw_shader.GetValue();
VideoCore::g_separable_shader_enabled = values.separable_shader.GetValue();
VideoCore::g_hw_shader_accurate_mul = values.shaders_accurate_mul.GetValue();
VideoCore::g_use_disk_shader_cache = values.use_disk_shader_cache.GetValue();
VideoCore::g_texture_filter_update_requested = true;
#ifndef ANDROID
if (VideoCore::g_renderer) {
@ -51,10 +60,12 @@ void Apply() {
}
#endif
VideoCore::g_renderer_bg_color_update_requested = true;
VideoCore::g_renderer_sampler_update_requested = true;
VideoCore::g_renderer_shader_update_requested = true;
VideoCore::g_texture_filter_update_requested = true;
if (VideoCore::g_renderer) {
auto& settings = VideoCore::g_renderer->Settings();
settings.bg_color_update_requested = true;
settings.sampler_update_requested = true;
settings.shader_update_requested = true;
}
auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) {
@ -100,7 +111,10 @@ void LogSettings() {
LOG_INFO(Config, "Citra Configuration:");
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
log_setting("Renderer_UseGLES", values.use_gles.GetValue());
log_setting("Renderer_GraphicsAPI", GetAPIName(values.graphics_api.GetValue()));
log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue());
log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue());
log_setting("Renderer_Debug", values.renderer_debug.GetValue());
log_setting("Renderer_UseHwRenderer", values.use_hw_renderer.GetValue());
log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue());
log_setting("Renderer_SeparableShader", values.separable_shader.GetValue());
@ -188,7 +202,10 @@ void RestoreGlobalState(bool is_powered_on) {
// Renderer
values.use_hw_renderer.SetGlobal(true);
values.use_hw_shader.SetGlobal(true);
values.graphics_api.SetGlobal(true);
values.physical_device.SetGlobal(true);
values.separable_shader.SetGlobal(true);
values.async_shader_compilation.SetGlobal(true);
values.use_disk_shader_cache.SetGlobal(true);
values.shaders_accurate_mul.SetGlobal(true);
values.use_vsync_new.SetGlobal(true);

View File

@ -15,6 +15,12 @@
namespace Settings {
enum class GraphicsAPI {
OpenGL = 0,
OpenGLES = 1,
Vulkan = 2,
};
enum class InitClock : u32 {
SystemTime = 0,
FixedTime = 1,
@ -415,7 +421,12 @@ struct Values {
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
// Renderer
Setting<bool> use_gles{false, "use_gles"};
SwitchableSetting<GraphicsAPI> graphics_api{GraphicsAPI::OpenGL, "graphics_api"};
SwitchableSetting<u32> physical_device{0, "physical_device"};
Setting<bool> renderer_debug{false, "renderer_debug"};
Setting<bool> dump_command_buffers{false, "dump_command_buffers"};
SwitchableSetting<bool> spirv_shader_gen{true, "spirv_shader_gen"};
SwitchableSetting<bool> async_shader_compilation{false, "async_shader_compilation"};
SwitchableSetting<bool> use_hw_renderer{true, "use_hw_renderer"};
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
SwitchableSetting<bool> separable_shader{false, "use_separable_shader"};

View File

@ -8,6 +8,7 @@
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include "common/common_types.h"
namespace Common::Telemetry {
@ -52,8 +53,8 @@ public:
template <typename T>
class Field : public FieldInterface {
public:
Field(FieldType type, std::string name, T value)
: name(std::move(name)), type(type), value(std::move(value)) {}
Field(FieldType type, std::string_view name, T value)
: name(name), type(type), value(std::move(value)) {}
Field(const Field&) = default;
Field& operator=(const Field&) = default;
@ -115,7 +116,7 @@ public:
* @param value Value for the field to add.
*/
template <typename T>
void AddField(FieldType type, const char* name, T value) {
void AddField(FieldType type, std::string_view name, T value) {
return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
}

View File

@ -1,8 +1,10 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/common_funcs.h"
#include <string>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
@ -20,7 +22,6 @@
#ifndef _WIN32
#include <unistd.h>
#endif
#include <string>
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
@ -28,6 +29,56 @@
namespace Common {
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {
auto handle = GetCurrentThread();
int windows_priority = 0;
switch (new_priority) {
case ThreadPriority::Low:
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::High:
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::VeryHigh:
windows_priority = THREAD_PRIORITY_HIGHEST;
break;
case ThreadPriority::Critical:
windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
break;
default:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
}
SetThreadPriority(handle, windows_priority);
}
#else
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
const auto scheduling_type = SCHED_OTHER;
s32 max_prio = sched_get_priority_max(scheduling_type);
s32 min_prio = sched_get_priority_min(scheduling_type);
u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
pthread_setschedparam(this_thread, scheduling_type, &params);
}
#endif
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
@ -47,7 +98,7 @@ void SetCurrentThreadName(const char* name) {
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = static_cast<DWORD>(-1);
info.dwThreadID = std::numeric_limits<DWORD>::max();
info.dwFlags = 0;
__try {
@ -81,6 +132,12 @@ void SetCurrentThreadName(const char* name) {
}
#endif
#if defined(_WIN32)
void SetCurrentThreadName(const char* name) {
// Do Nothing on MingW
}
#endif
#endif
} // namespace Common

View File

@ -1,6 +1,6 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -10,13 +10,15 @@
#include <cstddef>
#include <mutex>
#include <thread>
#include "common/common_types.h"
#include "common/polyfill_thread.h"
namespace Common {
class Event {
public:
void Set() {
std::lock_guard lk{mutex};
std::scoped_lock lk{mutex};
if (!is_set) {
is_set = true;
condvar.notify_one();
@ -29,8 +31,7 @@ public:
is_set = false;
}
template <class Duration>
bool WaitFor(const std::chrono::duration<Duration>& time) {
bool WaitFor(const std::chrono::nanoseconds& time) {
std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
@ -54,6 +55,10 @@ public:
is_set = false;
}
[[nodiscard]] bool IsSet() {
return is_set;
}
private:
std::condition_variable condvar;
std::mutex mutex;
@ -65,7 +70,7 @@ public:
explicit Barrier(std::size_t count_) : count(count_) {}
/// Blocks until all "count" threads have called Sync()
void Sync() {
bool Sync(std::stop_token token = {}) {
std::unique_lock lk{mutex};
const std::size_t current_generation = generation;
@ -73,25 +78,37 @@ public:
generation++;
waiting = 0;
condvar.notify_all();
return true;
} else {
condvar.wait(lk,
[this, current_generation] { return current_generation != generation; });
CondvarWait(condvar, lk, token,
[this, current_generation] { return current_generation != generation; });
return !token.stop_requested();
}
}
std::size_t Generation() const {
std::unique_lock lk(mutex);
std::size_t Generation() {
std::unique_lock lk{mutex};
return generation;
}
private:
std::condition_variable condvar;
mutable std::mutex mutex;
std::condition_variable_any condvar;
std::mutex mutex;
std::size_t count;
std::size_t waiting = 0;
std::size_t generation = 0; // Incremented once each time the barrier is used
};
enum class ThreadPriority : u32 {
Low = 0,
Normal = 1,
High = 2,
VeryHigh = 3,
Critical = 4,
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
} // namespace Common

121
src/common/thread_worker.h Normal file
View File

@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include <queue>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "common/unique_function.h"
namespace Common {
template <class StateType = void>
class StatefulThreadWorker {
static constexpr bool with_state = !std::is_same_v<StateType, void>;
struct DummyCallable {
int operator()() const noexcept {
return 0;
}
};
using Task =
std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>;
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
public:
explicit StatefulThreadWorker(size_t num_workers, std::string_view name, StateMaker func = {})
: workers_queued{num_workers}, thread_name{name} {
const auto lambda = [this, func](std::stop_token stop_token) {
Common::SetCurrentThreadName(thread_name.data());
{
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
while (!stop_token.stop_requested()) {
Task task;
{
std::unique_lock lock{queue_mutex};
if (requests.empty()) {
wait_condition.notify_all();
}
Common::CondvarWait(condition, lock, stop_token,
[this] { return !requests.empty(); });
if (stop_token.stop_requested()) {
break;
}
task = std::move(requests.front());
requests.pop();
}
if constexpr (with_state) {
task(&state);
} else {
task();
}
++work_done;
}
}
++workers_stopped;
wait_condition.notify_all();
};
threads.reserve(num_workers);
for (size_t i = 0; i < num_workers; ++i) {
threads.emplace_back(lambda);
}
}
StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete;
StatefulThreadWorker(const StatefulThreadWorker&) = delete;
StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete;
StatefulThreadWorker(StatefulThreadWorker&&) = delete;
void QueueWork(Task work) {
{
std::unique_lock lock{queue_mutex};
requests.emplace(std::move(work));
++work_scheduled;
}
condition.notify_one();
}
void WaitForRequests(std::stop_token stop_token = {}) {
std::stop_callback callback(stop_token, [this] {
for (auto& thread : threads) {
thread.request_stop();
}
});
std::unique_lock lock{queue_mutex};
wait_condition.wait(lock, [this] {
return workers_stopped >= workers_queued || work_done >= work_scheduled;
});
}
const std::size_t NumWorkers() const noexcept {
return threads.size();
}
private:
std::queue<Task> requests;
std::mutex queue_mutex;
std::condition_variable_any condition;
std::condition_variable wait_condition;
std::atomic<size_t> work_scheduled{};
std::atomic<size_t> work_done{};
std::atomic<size_t> workers_stopped{};
std::atomic<size_t> workers_queued{};
std::string_view thread_name;
std::vector<std::jthread> threads;
};
using ThreadWorker = StatefulThreadWorker<>;
} // namespace Common

View File

@ -13,8 +13,10 @@
#include <mutex>
#include <utility>
#include "common/polyfill_thread.h"
namespace Common {
template <typename T>
template <typename T, bool with_stop_token = false>
class SPSCQueue {
public:
SPSCQueue() {
@ -40,21 +42,19 @@ public:
template <typename Arg>
void Push(Arg&& t) {
// create the element, add it to the queue
write_ptr->current = std::forward<Arg>(t);
write_ptr->current = std::move(t);
// set the next pointer to a new element ptr
// then advance the write pointer
ElementPtr* new_ptr = new ElementPtr();
write_ptr->next.store(new_ptr, std::memory_order_release);
write_ptr = new_ptr;
++size;
const size_t previous_size{size++};
// Acquire the mutex and then immediately release it as a fence.
// cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
// line before cv.wait
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
if (previous_size == 0) {
std::lock_guard lock{cv_mutex};
}
std::scoped_lock lock{cv_mutex};
cv.notify_one();
}
@ -83,10 +83,27 @@ public:
return true;
}
T PopWait() {
void Wait() {
if (Empty()) {
std::unique_lock lock{cv_mutex};
cv.wait(lock, [this]() { return !Empty(); });
cv.wait(lock, [this] { return !Empty(); });
}
}
T PopWait() {
Wait();
T t;
Pop(t);
return t;
}
T PopWait(std::stop_token stop_token) {
if (Empty()) {
std::unique_lock lock{cv_mutex};
Common::CondvarWait(cv, lock, stop_token, [this] { return !Empty(); });
}
if (stop_token.stop_requested()) {
return T{};
}
T t;
Pop(t);
@ -105,7 +122,7 @@ private:
// and a pointer to the next ElementPtr
class ElementPtr {
public:
ElementPtr() = default;
ElementPtr() {}
~ElementPtr() {
ElementPtr* next_ptr = next.load();
@ -121,13 +138,13 @@ private:
ElementPtr* read_ptr;
std::atomic_size_t size{0};
std::mutex cv_mutex;
std::condition_variable cv;
std::conditional_t<with_stop_token, std::condition_variable_any, std::condition_variable> cv;
};
// a simple thread-safe,
// single reader, multiple writer queue
template <typename T>
template <typename T, bool with_stop_token = false>
class MPSCQueue {
public:
[[nodiscard]] std::size_t Size() const {
@ -144,7 +161,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
std::lock_guard lock{write_lock};
std::scoped_lock lock{write_lock};
spsc_queue.Push(t);
}
@ -156,17 +173,25 @@ public:
return spsc_queue.Pop(t);
}
void Wait() {
spsc_queue.Wait();
}
T PopWait() {
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
return spsc_queue.PopWait(stop_token);
}
// not thread-safe
void Clear() {
spsc_queue.Clear();
}
private:
SPSCQueue<T> spsc_queue;
SPSCQueue<T, with_stop_token> spsc_queue;
std::mutex write_lock;
};
} // namespace Common

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <utility>
namespace Common {
/// General purpose function wrapper similar to std::function.
/// Unlike std::function, the captured values don't have to be copyable.
/// This class can be moved but not copied.
template <typename ResultType, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual ResultType operator()(Args&&...) = 0;
};
template <typename Functor>
class Callable final : public CallableBase {
public:
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
~Callable() override = default;
ResultType operator()(Args&&... args) override {
return functor(std::forward<Args>(args)...);
}
private:
Functor functor;
};
public:
UniqueFunction() = default;
template <typename Functor>
UniqueFunction(Functor&& functor)
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
UniqueFunction& operator=(const UniqueFunction&) = delete;
UniqueFunction(const UniqueFunction&) = delete;
ResultType operator()(Args&&... args) const {
return (*callable)(std::forward<Args>(args)...);
}
explicit operator bool() const noexcept {
return static_cast<bool>(callable);
}
private:
std::unique_ptr<CallableBase> callable;
};
} // namespace Common

View File

@ -36,8 +36,6 @@ add_library(core STATIC
core.h
core_timing.cpp
core_timing.h
custom_tex_cache.cpp
custom_tex_cache.h
dumping/backend.cpp
dumping/backend.h
file_sys/archive_backend.cpp
@ -113,8 +111,6 @@ add_library(core STATIC
frontend/input.h
frontend/mic.cpp
frontend/mic.h
frontend/scope_acquire_context.cpp
frontend/scope_acquire_context.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
hle/applets/applet.cpp

View File

@ -12,7 +12,6 @@
#include "audio_core/lle/lle.h"
#include "common/arch.h"
#include "common/logging/log.h"
#include "common/texture.h"
#include "core/arm/arm_interface.h"
#include "core/arm/exclusive_monitor.h"
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
@ -27,7 +26,6 @@
#include "core/dumping/ffmpeg_backend.h"
#endif
#include "common/settings.h"
#include "core/custom_tex_cache.h"
#include "core/gdbstub/gdbstub.h"
#include "core/global.h"
#include "core/hle/kernel/client_port.h"
@ -48,6 +46,7 @@
#include "core/movie.h"
#include "core/rpc/rpc_server.h"
#include "network/network.h"
#include "video_core/rasterizer_cache/custom_tex_manager.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@ -317,16 +316,9 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
static_cast<u32>(load_result));
}
perf_stats = std::make_unique<PerfStats>(title_id);
custom_tex_cache = std::make_unique<Core::CustomTexCache>();
if (Settings::values.custom_textures) {
const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id;
FileUtil::CreateFullPath(fmt::format(
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id));
custom_tex_cache->FindCustomTextures(program_id);
}
if (Settings::values.preload_textures) {
custom_tex_cache->PreloadTextures(*GetImageInterface());
custom_tex_manager->FindCustomTextures();
}
status = ResultStatus::Success;
@ -431,13 +423,13 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
video_dumper = std::make_unique<VideoDumper::NullBackend>();
#endif
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *memory);
custom_tex_manager = std::make_unique<VideoCore::CustomTexManager>(*this);
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *this);
if (result != VideoCore::ResultStatus::Success) {
switch (result) {
case VideoCore::ResultStatus::ErrorGenericDrivers:
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
case VideoCore::ResultStatus::ErrorBelowGL43:
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
default:
return ResultStatus::ErrorVideoCore;
}
@ -450,7 +442,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
return ResultStatus::Success;
}
RendererBase& System::Renderer() {
VideoCore::RendererBase& System::Renderer() {
return *VideoCore::g_renderer;
}
@ -514,12 +506,12 @@ const VideoDumper::Backend& System::VideoDumper() const {
return *video_dumper;
}
Core::CustomTexCache& System::CustomTexCache() {
return *custom_tex_cache;
VideoCore::CustomTexManager& System::CustomTexManager() {
return *custom_tex_manager;
}
const Core::CustomTexCache& System::CustomTexCache() const {
return *custom_tex_cache;
const VideoCore::CustomTexManager& System::CustomTexManager() const {
return *custom_tex_manager;
}
void System::RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector) {
@ -530,10 +522,6 @@ void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard
registered_swkbd = std::move(swkbd);
}
void System::RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface) {
registered_image_interface = std::move(image_interface);
}
void System::Shutdown(bool is_deserializing) {
// Log last frame performance stats
const auto perf_results = GetAndResetPerfStats();

View File

@ -9,10 +9,8 @@
#include <string>
#include <boost/serialization/version.hpp>
#include "common/common_types.h"
#include "core/custom_tex_cache.h"
#include "core/frontend/applets/mii_selector.h"
#include "core/frontend/applets/swkbd.h"
#include "core/frontend/image_interface.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/perf_stats.h"
@ -57,7 +55,10 @@ namespace VideoDumper {
class Backend;
}
namespace VideoCore {
class CustomTexManager;
class RendererBase;
} // namespace VideoCore
namespace Core {
@ -90,8 +91,6 @@ public:
ErrorVideoCore, ///< Error in the video core
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
/// generic drivers installed
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
/// OpenGL 4.3 or higher
ErrorSavestate, ///< Error saving or loading
ShutdownRequested, ///< Emulated program requested a system shutdown
ErrorUnknown ///< Any other error
@ -210,7 +209,7 @@ public:
return *dsp_core;
}
[[nodiscard]] RendererBase& Renderer();
[[nodiscard]] VideoCore::RendererBase& Renderer();
/**
* Gets a reference to the service manager.
@ -257,11 +256,11 @@ public:
/// Gets a const reference to the cheat engine
[[nodiscard]] const Cheats::CheatEngine& CheatEngine() const;
/// Gets a reference to the custom texture cache system
[[nodiscard]] Core::CustomTexCache& CustomTexCache();
/// Gets a reference to the custom texture management system
[[nodiscard]] VideoCore::CustomTexManager& CustomTexManager();
/// Gets a const reference to the custom texture cache system
[[nodiscard]] const Core::CustomTexCache& CustomTexCache() const;
/// Gets a const reference to the custom texture management system
[[nodiscard]] const VideoCore::CustomTexManager& CustomTexManager() const;
/// Gets a reference to the video dumper backend
[[nodiscard]] VideoDumper::Backend& VideoDumper();
@ -301,14 +300,6 @@ public:
return registered_swkbd;
}
/// Image interface
void RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface);
[[nodiscard]] std::shared_ptr<Frontend::ImageInterface> GetImageInterface() const {
return registered_image_interface;
}
void SaveState(u32 slot) const;
void LoadState(u32 slot);
@ -367,10 +358,7 @@ private:
std::unique_ptr<VideoDumper::Backend> video_dumper;
/// Custom texture cache system
std::unique_ptr<Core::CustomTexCache> custom_tex_cache;
/// Image interface
std::shared_ptr<Frontend::ImageInterface> registered_image_interface;
std::unique_ptr<VideoCore::CustomTexManager> custom_tex_manager;
/// RPC Server for scripting support
std::unique_ptr<RPC::RPCServer> rpc_server;

View File

@ -1,109 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/file_util.h"
#include "common/texture.h"
#include "core.h"
#include "core/custom_tex_cache.h"
namespace Core {
CustomTexCache::CustomTexCache() = default;
CustomTexCache::~CustomTexCache() = default;
bool CustomTexCache::IsTextureDumped(u64 hash) const {
return dumped_textures.count(hash);
}
void CustomTexCache::SetTextureDumped(const u64 hash) {
dumped_textures.insert(hash);
}
bool CustomTexCache::IsTextureCached(u64 hash) const {
return custom_textures.count(hash);
}
const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const {
return custom_textures.at(hash);
}
void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
custom_textures[hash] = {width, height, tex};
}
void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) {
if (custom_texture_paths.count(hash))
LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path);
else
custom_texture_paths[hash] = {path, hash};
}
void CustomTexCache::FindCustomTextures(u64 program_id) {
// Custom textures are currently stored as
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
const std::string load_path = fmt::format(
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id);
if (FileUtil::Exists(load_path)) {
FileUtil::FSTEntry texture_dir;
std::vector<FileUtil::FSTEntry> textures;
// 64 nested folders should be plenty for most cases
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
for (const auto& file : textures) {
if (file.isDirectory)
continue;
if (file.virtualName.substr(0, 5) != "tex1_")
continue;
u32 width;
u32 height;
u64 hash;
u32 format; // unused
// TODO: more modern way of doing this
if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
&hash, &format) == 4) {
AddTexturePath(hash, file.physicalName);
}
}
}
}
void CustomTexCache::PreloadTextures(Frontend::ImageInterface& image_interface) {
for (const auto& path : custom_texture_paths) {
const auto& path_info = path.second;
Core::CustomTexInfo tex_info;
if (image_interface.DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
path_info.path)) {
// Make sure the texture size is a power of 2
std::bitset<32> width_bits(tex_info.width);
std::bitset<32> height_bits(tex_info.height);
if (width_bits.count() == 1 && height_bits.count() == 1) {
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
} else {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
}
} else {
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
}
}
}
bool CustomTexCache::CustomTextureExists(u64 hash) const {
return custom_texture_paths.count(hash);
}
const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const {
return custom_texture_paths.at(hash);
}
bool CustomTexCache::IsTexturePathMapEmpty() const {
return custom_texture_paths.size() == 0;
}
} // namespace Core

View File

@ -1,55 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "common/common_types.h"
namespace Frontend {
class ImageInterface;
} // namespace Frontend
namespace Core {
struct CustomTexInfo {
u32 width;
u32 height;
std::vector<u8> tex;
};
// This is to avoid parsing the filename multiple times
struct CustomTexPathInfo {
std::string path;
u64 hash;
};
// TODO: think of a better name for this class...
class CustomTexCache {
public:
explicit CustomTexCache();
~CustomTexCache();
bool IsTextureDumped(u64 hash) const;
void SetTextureDumped(u64 hash);
bool IsTextureCached(u64 hash) const;
const CustomTexInfo& LookupTexture(u64 hash) const;
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
void AddTexturePath(u64 hash, const std::string& path);
void FindCustomTextures(u64 program_id);
void PreloadTextures(Frontend::ImageInterface& image_interface);
bool CustomTextureExists(u64 hash) const;
const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const;
bool IsTexturePathMapEmpty() const;
private:
std::unordered_set<u64> dumped_textures;
std::unordered_map<u64, CustomTexInfo> custom_textures;
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
};
} // namespace Core

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