Compare commits

...

22 Commits

Author SHA1 Message Date
CamilleLaVey 3352f0662c [vulkan] Correction on unconditional/ harcoded makeview 2D array vs compute shader 3D views 2026-07-03 17:54:55 -04:00
CamilleLaVey 95e42257f7 [vulkan] Added buffer memory barrier to keep track of previous buffer's active transfer 2026-07-03 16:47:49 -04:00
CamilleLaVey 8961bc37f3 [vulkan] Added transfer bits to compute memory barriers 2026-07-03 15:37:19 -04:00
CamilleLaVey e1e8ae68bf [vulkan] First intent on unswizzling via gpu shaders 2026-07-03 03:25:45 -04:00
lizzie 4c65780f11 Revert "[common/dynamic_library] fix AUR build error (#4156)" (#4157)
This reverts commit a9c4c8aefd.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4157
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-07-02 22:07:37 +02:00
lizzie a9c4c8aefd [common/dynamic_library] fix AUR build error (#4156)
not gonna question why there was an ifdef there

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4156
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-07-02 19:49:19 +02:00
lizzie a769505a45 [vk, ogl] Remove dedicated precomputed swizzle table; use shorter inline table in block_linear swizzle shaders (#4146)
old table = 64 * 8 * sizeof(u32) = 512 * 4 = 4096 bytes
new table = 8 * sizeof(u32) = 8 * 4 = 32 bytes

the expression
```glsl
    return ((pos & 0x0180) >> 1)
         | ((pos & 0x0040) >> 2)
         | ((pos & 0x0020) << 3)
         | ((pos & 0x0010) << 1)
         | ((pos & 0x000f) << 0);
```
is equivalent for generating the table but idk if we'd want that

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4146
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-07-02 19:24:07 +02:00
xbzk 54c3d10b86 [vk, renderdoc] (VUID-02997) avoid vk_image_view as VK_NULL_HANDLE when feature nullDescriptor is unavailable (#4056)
This minor change in src\video_core\renderer_vulkan\pipeline_helper.h allows renderdoc capture on mhr sunbreak.
Maybe it sanitizes some crashes on old vulkan GPUs.

Device log (nvidia kepler gpu vulkan 1.1.117):
[  17.605262] Render.Vulkan <Info> video_core\vulkan_common\vulkan_device.cpp:1041:GetSuitability: Device doesn't support feature nullDescriptor

VUID-VkWriteDescriptorSet-descriptorType-02997
For sampled/combined/storage image descriptors, Khronos says imageView must be valid or VK_NULL_HANDLE
(https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceRobustness2FeaturesKHR.html)

but then it says: if nullDescriptor is not enabled, imageView must not be VK_NULL_HANDLE.
(https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html)
nullDescriptor is exactly the feature that allows descriptors to be written with null handles.

The fix relies on conditionally replacing the null sampled view with Eden’s dummy null-image-view object (ImageView constructor checks HasNullDescriptor which checks features.robustness2.nullDescriptor. cheap checks, and only for null handles, so no notable cost).

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4056
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-02 03:23:08 +02:00
xbzk bdf60d79a0 [video_core] gate Mesa EXIT terminator detection behind non-proprietary driver (#4151)
FIXES A REGRESSION FROM 4012.

TryFindSize's Mesa fallback matched the unconditional @PT EXIT (0xE30000000007000F) that nv50_ir emits at program end.
That exact word is also emitted by NVN mid-program for unconditional early-outs, so on retail titles (e.g. Super Mario 3D World) the scan truncated at the first early EXIT and dropped the rest of the shader, making textures vanish.

Mesa's terminal EXIT and NVN's mid-program EXIT are bit-identical, so no mask can separate them. Gate the heuristic on !is_proprietary_driver:

NVN binaries (bindless cbuf slot 2) keep self-branch-only sizing, while nouveau/Mesa homebrew (single terminal EXIT, no self-branch trailer) still uses the EXIT terminator. nv50_ir emits exactly one EXIT (early returns lower to BRA-to-exit, discard to DISCARD), so a single terminal match is correct for that path.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4151
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-07-01 13:06:00 +02:00
lizzie 2068b5d452 [vk] Clamp dynamic descriptors based on device limits (#4115)
Should fix this specific crash
```
* thread #82, name = 'GPU', stop reason = Exception 0xc0000005 encountered at address 0x7ff7e5193e89: Access violation reading location 0x00000098
  * frame #0: 0x00007ff7e5193e89 eden.exe`std::unique_ptr<Vulkan::Scheduler::CommandChunk,std::default_delete<Vulkan::Scheduler::CommandChunk> >::operator->(this=<unavailable>) at memory:3453 [inlined]
    frame #1: 0x00007ff7e5193e81 eden.exe`void Vulkan::Scheduler::DispatchWork(this=0x0000000000000000) at vk_scheduler.cpp:146
    frame #2: 0x00007ff7e5193d8f eden.exe`void Vulkan::Scheduler::WaitWorker(this=<unavailable>) at vk_scheduler.cpp:133
    frame #3: 0x00007ff7e54f472e eden.exe`void Vulkan::UpdateDescriptorQueue::Acquire(this=0x0000026c77cfb4b8) at vk_update_descriptor.cpp:41
    frame #4: 0x00007ff7e5537a43 eden.exe`void Vulkan::ASTCDecoderPass::Assemble(this=0x0000026c794fba10, image=0x0000026cfb794850, map=0x00000025e28fcb30, swizzles=<unavailable>) at vk_compute_pass.cpp:603
    frame #5: 0x00007ff7e55043e1 eden.exe`void Vulkan::TextureCacheRuntime::AccelerateImageUpload(this=<unavailable>, image=0x0000026cfb794850, map=0x00000025e28fcb30, swizzles=size=10, z_start=0, z_count=0) at vk_texture_cache.cpp:2482
    frame #6: 0x00007ff7e5516592 eden.exe`void VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::UploadImageContents<struct Vulkan::StagingBufferRef>(this=<unavailable>, image=0x0000026cfb794850, staging=0x00000025e28fcb30) at texture_cache.h:1147
    frame #7: 0x00007ff7e551523d eden.exe`void VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::RefreshContents(this=0x0000026c794fd800, image=0x0000026cfb794850, image_id=(index = 3801072224)) at texture_cache.h:1133
    frame #8: 0x00007ff7e5517b50 eden.exe`struct Common::SlotId VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::JoinImages(this=0x0000026c794fd800, info=<unavailable>, gpu_addr=25374426112, cpu_addr=2254181376) at texture_cache.h:1644
    frame #9: 0x00007ff7e5516ff6 eden.exe`struct Common::SlotId VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::InsertImage(this=0x0000026c794fd800, info=0x00000025e28fd270, gpu_addr=25374426112, options=0x0) at texture_cache.h:1513
    frame #10: 0x00007ff7e5516a60 eden.exe`struct Common::SlotId VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::FindOrInsertImage(this=0x0000026c794fd800, info=0x00000025e28fd270, gpu_addr=25374426112, options=0x0) at texture_cache.h:1194
    frame #11: 0x00007ff7e5515a13 eden.exe`struct Common::SlotId VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::CreateImageView(this=0x0000026c794fd800, config=0x00000025e28fd370) at texture_cache.h:1173
    frame #12: 0x00007ff7e550cd64 eden.exe`struct Common::SlotId VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::VisitImageView(this=0x0000026c794fd800, index=4586, compute=<unavailable>) at texture_cache.h:554
    frame #13: 0x00007ff7e550d181 eden.exe`void VideoCommon::TextureCache<struct Vulkan::TextureCacheParams>::FillImageViews(this=0x0000026c794fd800, views=size=5, compute=<unavailable>, blacklist=<unavailable>) at texture_cache.h:227
    frame #14: 0x00007ff7e58896e4 eden.exe`Vulkan::GraphicsPipeline::ConfigureImpl<Vulkan::(anonymous namespace)::SimpleStorageSpec>(this=0x0000026f865c1a60, is_indexed=<unavailable>) at vk_graphics_pipeline.cpp:415
    frame #15: 0x00007ff7e5889479 eden.exe`<lambda_1>::operator(pl=<unavailable>, is_indexed=<unavailable>) at vk_graphics_pipeline.h:123 [inlined]
    frame #16: 0x00007ff7e5889474 eden.exe`<lambda_1>::__invoke(pl=<unavailable>, is_indexed=<unavailable>) at vk_graphics_pipeline.h:123
    frame #17: 0x00007ff7e519d2a8 eden.exe`Vulkan::GraphicsPipeline::Configure(this=0x0000026f865c1a60, is_indexed=<unavailable>) at vk_graphics_pipeline.h:105 [inlined]
    frame #18: 0x00007ff7e519d29d eden.exe`Vulkan::RasterizerVulkan::PrepareDraw<`lambda at D:\a\g\g\eden-source\src\video_core\renderer_vulkan\vk_rasterizer.cpp:256:29'>(this=0x0000026c764f9368, is_indexed=<unavailable>, draw_func=0x00000025e28fdb80) at vk_rasterizer.cpp:244
    frame #19: 0x00007ff7e519d1db eden.exe`void Vulkan::RasterizerVulkan::Draw(this=<unavailable>, is_indexed=<unavailable>, instance_count=<unavailable>) at vk_rasterizer.cpp:256
    frame #20: 0x00007ff7e50d9025 eden.exe`void Tegra::HLE_DrawIndexedIndirect::Fallback(this=0x0000026f2af85118, maxwell3d=0x0000026ccbf9eb00, parameters=size=6) at macro.cpp:178
    frame #21: 0x00007ff7e50d8f21 eden.exe`void Tegra::HLE_DrawIndexedIndirect::Execute(this=0x0000026f2af85118, maxwell3d=0x0000026ccbf9eb00, parameters=size=6) at macro.cpp:129
    frame #22: 0x00007ff7e50db607 eden.exe`Tegra::MacroEngine::Execute::<lambda_0>::operator(this=0x00000025e28fde70, acm= Active Type = Tegra::HLE_DrawIndexedIndirect ) at macro.cpp:1362
    frame #23: 0x00007ff7e50dada0 eden.exe`void Tegra::MacroEngine::Execute(this=0x0000026ccbfaa020, maxwell3d=0x0000026ccbf9eb00, method=418, parameters=<unavailable>) at macro.cpp:1392
    frame #24: 0x00007ff7e4ef062b eden.exe`void Tegra::Engines::Maxwell3D::CallMacroMethod(this=0x0000026ccbf9eb00, method=<unavailable>, parameters=<unavailable>) at maxwell_3d.cpp:390
    frame #25: 0x00007ff7e4ef04be eden.exe`void Tegra::Engines::Maxwell3D::ProcessMacro(this=0x0000026ccbf9eb00, method=<unavailable>, base_start=<unavailable>, amount=5, is_last_call=<unavailable>) at maxwell_3d.cpp:223
    frame #26: 0x00007ff7e4ef1673 eden.exe`void Tegra::Engines::Maxwell3D::CallMultiMethod(this=<unavailable>, method=<unavailable>, base_start=0x000001ea398e22f4, amount=<unavailable>, methods_pending=5) at maxwell_3d.cpp:419
    frame #27: 0x00007ff7e4ef535a eden.exe`void Tegra::DmaPusher::CallMultiMethod(this=<unavailable>, base_start=0x000001ea398e22f4, num_methods=<unavailable>) const at dma_pusher.cpp:201
    frame #28: 0x00007ff7e4ef5023 eden.exe`void Tegra::DmaPusher::ProcessCommands(this=0x0000026ccbfba698, commands=size=36) at dma_pusher.cpp:120
    frame #29: 0x00007ff7e4ef4ced eden.exe`bool Tegra::DmaPusher::Step(this=0x0000026ccbfba698) at dma_pusher.cpp:88
    frame #30: 0x00007ff7e4ef49f8 eden.exe`void Tegra::DmaPusher::DispatchCalls(this=0x0000026ccbfba698) at dma_pusher.cpp:40
    frame #31: 0x00007ff7e4ede797 eden.exe`void Tegra::Control::Scheduler::Push(this=<unavailable>, channel=-493895072, entries=0x00000025e28fe2e0) at scheduler.cpp:31
    frame #32: 0x00007ff7e4eedc7b eden.exe`VideoCommon::GPUThread::ThreadManager::StartThread::<lambda_0>::operator(this=<unavailable>, stop_token=stop_token @ 0x00000025e28ffb40) at gpu_thread.cpp:42 [inlined]
    frame #33: 0x00007ff7e4eedae8 eden.exe`std::invoke(_Obj=<unavailable>, _Arg1=0x0000026ccb3edef0) at type_traits:1680 [inlined]
    frame #34: 0x00007ff7e4eedac1 eden.exe`std::thread::_Invoke<std::tuple<`lambda at D:\a\g\g\eden-source\src\video_core\gpu_thread.cpp:29:27',std::stop_token>,0,1>(_RawVals=0x0000026ccb3edef0) at thread:60
    frame #35: 0x00007ff8e87a37b0 ucrtbase.dll`wcsrchr + 336
    ```

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4115
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-06-30 04:33:30 +02:00
lizzie 1b482fa99b [hle] remove parent object reference data on Request/Response builders, pass by argument (#3774)
anyways this should remove a bunch of redundant code/stack usage for response builders (ahem, msvc)

SHOULD help games that use IPC heavily like PKZA and whatnot

biblical levels of performance greed

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3774
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-06-29 08:11:12 +02:00
xbzk b6ee847947 [hle/am] make Service::Process move-only to fix #3908 KProcess use-after-free (#4137)
#3908 changed process_creation.*, CreateProcess/CreateApplicationProcess, to return std::optional<Process> instead of std::unique_ptr<Process>, so the AM sites now transfer a Process by value via make_unique<Service::Process>(*std::move(opt)).

The consequence: Process owns a refcounted KProcess* but its user-declared dtor suppressed the implicit move ctor, so that "move" silently shallow-copied and the temporary's dtor Close()/RemoveProcess()'d the shared handle -> use-after-free.

It's seems to be user end based, so whether it crashes may depend on machine, compiler, allocator reuse, refcount slack, and the AM event-observer thread race, idk. It reliably crashed my MSVC build at launching games (cstack: ProcessHolder -> MultiWait -> KSynchronizationObject::Wait -> null) multiple times.

Fix: give Process a move ctor that steals the handle (nulling the source so the moved-from dtor is a no-op) and delete copy/move-assign, making the optional<->unique_ptr transfer safe.

Bonus: explicited delete for the 3 kinds of assignment: copy ctor (the one used in eden), copy assign and move assign (currently unused) to force compile error if they ever come to use.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4137
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-06-29 02:39:11 +02:00
wildcard 6c16440996 [vulkan] Fix EDS 0-2 (#4117)
EDS3 states were dynamic just because the driver supports them. But in EDS0-EDS2, we does not actually emit the EDS3 dynamic commands for them. So the pipeline builder skipped baking some fixed graphics state into the Vulkan pipeline, but the runtime also did not set that state dynamically. That leaves wrong Vulkan state during rendering causing rendering glitches in XC2.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4117
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-06-28 15:07:54 +02:00
lizzie 09c583506b [tests] Require backing base pointer to be nonnull for HostMemory tests (#4138)
A basic requirement, probably more helpful than outright crashing in the middle of a test.

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4138
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-06-28 08:01:41 +02:00
lizzie 894add43f3 [vk] fix PR5R black screen on qcom driver (#4120)
Different approach proposed by gido.

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4120
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-06-28 04:29:28 +02:00
Lizzie d142b5dd6a [android] Add Enable GPU Buffer Readback option to Android (#4132)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4132
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-06-28 03:37:23 +02:00
maufeat 0c2894eabf [hle] add: (re)winding application and revert option<Process> back to unique_ptr (#4134)
This add winding application function. Test this by opening qlaunch -> top left Profile -> edit profile picture -> go back (do not save if you are on fw21+, it corrupts the image)
And reverts in #3908 added `optional<Process>` to `unique_ptr<Process>`

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4134
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-06-27 16:47:46 +02:00
lizzie d8a8169eb2 [hle/bsd] do not use rust-result wannabe Expected in functions (#4075)
rust has Result<T,E> but we don't really need that in c++, also the header just sucks, objectively

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4075
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-06-27 08:50:24 +02:00
lizzie 81c6e56713 [core/am] ban y2k domain to make Civ7 boot web-appletless (#3868)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3868
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-06-27 08:46:18 +02:00
MaranBr b4b41ee62c [buffer_cache] Add option to control GPU buffer readback (#4126)
Added an option to control the GPU buffer readback, as it causes issues if the hardware cannot keep up with the additional workload.

Some games require this to render certain effects properly.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4126
2026-06-27 08:38:04 +02:00
simply0001 0d6a2158f0 [maxwell_3d] append inline index draw streams in bulk (#4083)
Inline index draws arrive as a batch but were processed one word at a time, so each index ran through the full per-method path and got appended a byte at a time.
Handle the whole batch in one pass instead, updating state once and appending all the indices together. Replay shadow control still goes word by word since it has to reload each one.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4083
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-06-27 02:52:59 +02:00
xbzk 09b6b3b71e xbzk/gpu-logging_qt-controls_android-fix (#4018)
5af7771f83-Bugfix: Made gpu_log_level global-only (was per-game switchable). Fixed Android non-determinism where a per-game profile silently overrode the global to Off and trapped GPULogger::Initialize() in a dead state, making shader dumps fail invisibly. Android per-game UI now hides the whole GPU logging block; Qt UI is untouched (global-only anyway).

bf4aabe8ab-Refactor/Cleanup: Removed gpu_logging_enabled master toggle as redundant with gpu_log_level == Off. Introduced GPU::Logging::IsActive() helper, replaced 14 call sites across vk_*.cpp. Refactored LogShaderCompilation() to be text-only and extracted SPIR-V dumping into a standalone GPU::Logging::DumpSpirvShader() free function. No singleton dependency, gated only by gpu_log_shader_dumps. Now gpu_log_level and gpu_log_shader_dumps are fully orthogonal. Cleaned up Android (BooleanSetting, SettingsItem, presenter, 7 locale string files).

865a1c5027-Refactor: Renamed dump_shaders → dump_guest_shaders to disambiguate from gpu_log_shader_dumps. Updated Qt label to "Dump Guest (Maxwell) Shaders" and rewrote the tooltip to mention .ash, the DumpDir/shaders/ location, and nvdisasm.

7cab456fdf-Feature: Added Qt UI control for GPU log level in the Logging session. Added gpu_log_shader_dumps checkbox to the Graphics column right below dump_guest_shaders.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4018
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-06-27 02:52:13 +02:00
118 changed files with 1777 additions and 1877 deletions
@@ -31,6 +31,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"), ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
ENABLE_GPU_BUFFER_READBACK("enable_gpu_buffer_readback"),
SYNC_MEMORY_OPERATIONS("sync_memory_operations"), SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
BUFFER_REORDER_DISABLE("disable_buffer_reorder"), BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
RENDERER_DEBUG("debug"), RENDERER_DEBUG("debug"),
@@ -83,9 +84,10 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
ENABLE_OVERLAY("enable_overlay"), ENABLE_OVERLAY("enable_overlay"),
// GPU Logging // GPU Logging
GPU_LOGGING_ENABLED("gpu_logging_enabled"),
GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"), GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"),
GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"), GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"),
DUMP_GUEST_SHADERS("dump_guest_shaders"),
DUMP_MACROS("dump_macros"),
GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"), GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"),
GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug"), GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug"),
@@ -806,6 +806,13 @@ abstract class SettingsItem(
descriptionId = R.string.enable_buffer_history_description descriptionId = R.string.enable_buffer_history_description
) )
) )
put(
SwitchSetting(
BooleanSetting.ENABLE_GPU_BUFFER_READBACK,
titleId = R.string.enable_gpu_buffer_readback,
descriptionId = R.string.enable_gpu_buffer_readback_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS, BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS,
@@ -931,13 +938,6 @@ abstract class SettingsItem(
) )
// GPU Logging settings // GPU Logging settings
put(
SwitchSetting(
BooleanSetting.GPU_LOGGING_ENABLED,
titleId = R.string.gpu_logging_enabled,
descriptionId = R.string.gpu_logging_enabled_description
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
ByteSetting.GPU_LOG_LEVEL, ByteSetting.GPU_LOG_LEVEL,
@@ -954,6 +954,13 @@ abstract class SettingsItem(
descriptionId = R.string.gpu_log_vulkan_calls_description descriptionId = R.string.gpu_log_vulkan_calls_description
) )
) )
put(
SwitchSetting(
BooleanSetting.DUMP_GUEST_SHADERS,
titleId = R.string.dump_guest_shaders,
descriptionId = R.string.dump_guest_shaders_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.GPU_LOG_SHADER_DUMPS, BooleanSetting.GPU_LOG_SHADER_DUMPS,
@@ -961,6 +968,13 @@ abstract class SettingsItem(
descriptionId = R.string.gpu_log_shader_dumps_description descriptionId = R.string.gpu_log_shader_dumps_description
) )
) )
put(
SwitchSetting(
BooleanSetting.DUMP_MACROS,
titleId = R.string.dump_macros,
descriptionId = R.string.dump_macros_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.GPU_LOG_MEMORY_TRACKING, BooleanSetting.GPU_LOG_MEMORY_TRACKING,
@@ -292,6 +292,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key) add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
add(BooleanSetting.ENABLE_GPU_BUFFER_READBACK.key)
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key) add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
add(HeaderSetting(R.string.hacks)) add(HeaderSetting(R.string.hacks))
@@ -1288,14 +1289,17 @@ class SettingsFragmentPresenter(
add(ShortSetting.DEBUG_KNOBS.key) add(ShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key) add(StringSetting.PROGRAM_ARGS.key)
add(HeaderSetting(R.string.gpu_logging_header)) if (!NativeConfig.isPerGameConfigLoaded()) {
add(BooleanSetting.GPU_LOGGING_ENABLED.key) add(HeaderSetting(R.string.gpu_logging_header))
add(ByteSetting.GPU_LOG_LEVEL.key) add(ByteSetting.GPU_LOG_LEVEL.key)
add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key) add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key)
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key) add(BooleanSetting.DUMP_GUEST_SHADERS.key)
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key) add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key) add(BooleanSetting.DUMP_MACROS.key)
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key) add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
}
} }
} }
@@ -569,8 +569,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">تسجيل وحدة معالجة الرسومات</string> <string name="gpu_logging_header">تسجيل وحدة معالجة الرسومات</string>
<string name="gpu_logging_enabled">تمكين تسجيل وحدة معالجة الرسومات</string>
<string name="gpu_logging_enabled_description">تسجيل عمليات وحدة معالجة الرسومات في ملف eden_gpu.log لتصحيح أخطاء برامج تشغيل Adreno</string>
<string name="gpu_log_level">مستوى السجل</string> <string name="gpu_log_level">مستوى السجل</string>
<string name="gpu_log_level_description">مستوى التفاصيل لسجلات وحدة معالجة الرسومات (كلما زاد المستوى، زادت التفاصيل وزادت التكاليف الإضافية)</string> <string name="gpu_log_level_description">مستوى التفاصيل لسجلات وحدة معالجة الرسومات (كلما زاد المستوى، زادت التفاصيل وزادت التكاليف الإضافية)</string>
<string name="gpu_log_vulkan_calls">تسجيل استدعاءات واجهة برمجة تطبيقات Vulkan</string> <string name="gpu_log_vulkan_calls">تسجيل استدعاءات واجهة برمجة تطبيقات Vulkan</string>
@@ -563,8 +563,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">Registros de la GPU</string> <string name="gpu_logging_header">Registros de la GPU</string>
<string name="gpu_logging_enabled">Activar los registros de la GPU</string>
<string name="gpu_logging_enabled_description">Registra las operaciones de la GPU en eden_gpu.log para la depuración de los controladores de Adreno</string>
<string name="gpu_log_level">Nivel de registros</string> <string name="gpu_log_level">Nivel de registros</string>
<string name="gpu_log_level_description">Nivel de detalle de los registros de la GPU (más alto = más detalles, más sobrecarga)</string> <string name="gpu_log_level_description">Nivel de detalle de los registros de la GPU (más alto = más detalles, más sobrecarga)</string>
<string name="gpu_log_vulkan_calls">Registros de llamadas del API de Vulkan</string> <string name="gpu_log_vulkan_calls">Registros de llamadas del API de Vulkan</string>
@@ -525,7 +525,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">Journalisation GPU</string> <string name="gpu_logging_header">Journalisation GPU</string>
<string name="gpu_logging_enabled">Activer la journalisation GPU</string>
<string name="gpu_log_level">Niveau de journalisation</string> <string name="gpu_log_level">Niveau de journalisation</string>
<string name="gpu_log_vulkan_calls">Journaliser les appels API Vulkan</string> <string name="gpu_log_vulkan_calls">Journaliser les appels API Vulkan</string>
<string name="gpu_log_shader_dumps">Extraire les shaders</string> <string name="gpu_log_shader_dumps">Extraire les shaders</string>
@@ -562,8 +562,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">Ведение журнала ГПУ</string> <string name="gpu_logging_header">Ведение журнала ГПУ</string>
<string name="gpu_logging_enabled">Включить ведение журнала ГПУ</string>
<string name="gpu_logging_enabled_description">Записывать операции ГПУ в файл eden_gpu.log для отладки драйверов Adreno</string>
<string name="gpu_log_level">Уровень журналирования</string> <string name="gpu_log_level">Уровень журналирования</string>
<string name="gpu_log_level_description">Уровень детализации логов ГПУ (больше значение = больше деталей, выше нагрузка)</string> <string name="gpu_log_level_description">Уровень детализации логов ГПУ (больше значение = больше деталей, выше нагрузка)</string>
<string name="gpu_log_vulkan_calls">Записывать вызовы Vulkan API</string> <string name="gpu_log_vulkan_calls">Записывать вызовы Vulkan API</string>
@@ -565,8 +565,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">Журналювання ГП</string> <string name="gpu_logging_header">Журналювання ГП</string>
<string name="gpu_logging_enabled">Увімкнути журналювання ГП</string>
<string name="gpu_logging_enabled_description">Журналювати операції ГП до eden_gpu.log для зневадження драйверів Adreno</string>
<string name="gpu_log_level">Рівень журналювання</string> <string name="gpu_log_level">Рівень журналювання</string>
<string name="gpu_log_level_description">Рівень подробиць у журналі ГП (вищий = більше подробиць, більший вплив на швидкодію)</string> <string name="gpu_log_level_description">Рівень подробиць у журналі ГП (вищий = більше подробиць, більший вплив на швидкодію)</string>
<string name="gpu_log_vulkan_calls">Записувати виклики API Vulkan</string> <string name="gpu_log_vulkan_calls">Записувати виклики API Vulkan</string>
@@ -559,8 +559,6 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">GPU 日志</string> <string name="gpu_logging_header">GPU 日志</string>
<string name="gpu_logging_enabled">启用 GPU 日志</string>
<string name="gpu_logging_enabled_description">将 GPU 操作记录至 eden_gpu.log 以供调试 Adreno 驱动</string>
<string name="gpu_log_level">日志等级</string> <string name="gpu_log_level">日志等级</string>
<string name="gpu_log_level_description">GPU 日志的详细级别(数值越高 = 细节越多,开销越大)</string> <string name="gpu_log_level_description">GPU 日志的详细级别(数值越高 = 细节越多,开销越大)</string>
<string name="gpu_log_vulkan_calls">记录 Vulkan API 调用</string> <string name="gpu_log_vulkan_calls">记录 Vulkan API 调用</string>
@@ -503,6 +503,8 @@
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
<string name="enable_buffer_history">Enable buffer history</string> <string name="enable_buffer_history">Enable buffer history</string>
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string> <string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
<string name="enable_gpu_buffer_readback">Enable GPU Buffer Readback</string>
<string name="enable_gpu_buffer_readback_description">Preserves GPU-modified buffer data by reading it back before uploads. Some games require this to render certain effects properly. May cause issues if the hardware cannot handle the additional workload.</string>
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string> <string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string> <string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string>
@@ -575,14 +577,16 @@
<!-- GPU Logging strings --> <!-- GPU Logging strings -->
<string name="gpu_logging_header">GPU Logging</string> <string name="gpu_logging_header">GPU Logging</string>
<string name="gpu_logging_enabled">Enable GPU Logging</string>
<string name="gpu_logging_enabled_description">Log GPU operations to eden_gpu.log for debugging Adreno drivers</string>
<string name="gpu_log_level">Log Level</string> <string name="gpu_log_level">Log Level</string>
<string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string> <string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string>
<string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string> <string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string>
<string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string> <string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string>
<string name="gpu_log_shader_dumps">Dump Shaders</string> <string name="gpu_log_shader_dumps">Dump SPIR-V Shaders</string>
<string name="gpu_log_shader_dumps_description">Save compiled shader SPIR-V to files</string> <string name="gpu_log_shader_dumps_description">Save recompiled SPIR-V binaries (.spv) to dump folder. Inspect with spirv-dis/spirv-cross/spirv-val.</string>
<string name="dump_guest_shaders">Dump Guest (Maxwell) Shaders</string>
<string name="dump_guest_shaders_description">Save Maxwell guest shader bytecode files (*.ash) to dump folder. Inspect with nvdisasm.</string>
<string name="dump_macros">Dump Maxwell Macros</string>
<string name="dump_macros_description">Save Maxwell macro program files (*.macro) to dump folder. Inspect with envydis.</string>
<string name="gpu_log_memory_tracking">Track GPU Memory</string> <string name="gpu_log_memory_tracking">Track GPU Memory</string>
<string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string> <string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string>
<string name="gpu_log_driver_debug">Driver Debug Info</string> <string name="gpu_log_driver_debug">Driver Debug Info</string>
-1
View File
@@ -50,7 +50,6 @@ add_library(
elf.h elf.h
error.cpp error.cpp
error.h error.h
expected.h
fiber.cpp fiber.cpp
fiber.h fiber.h
fixed_point.h fixed_point.h
-986
View File
@@ -1,986 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// This is based on the proposed implementation of std::expected (P0323)
// https://github.com/TartanLlama/expected/blob/master/include/tl/expected.hpp
#pragma once
#include <type_traits>
#include <utility>
namespace Common {
template <typename T, typename E>
class Expected;
template <typename E>
class Unexpected {
public:
Unexpected() = delete;
constexpr explicit Unexpected(const E& e) : m_val{e} {}
constexpr explicit Unexpected(E&& e) : m_val{std::move(e)} {}
constexpr E& value() & {
return m_val;
}
constexpr const E& value() const& {
return m_val;
}
constexpr E&& value() && {
return std::move(m_val);
}
constexpr const E&& value() const&& {
return std::move(m_val);
}
private:
E m_val;
};
template <typename E>
constexpr auto operator<=>(const Unexpected<E>& lhs, const Unexpected<E>& rhs) {
return lhs.value() <=> rhs.value();
}
struct unexpect_t {
constexpr explicit unexpect_t() = default;
};
namespace detail {
struct no_init_t {
constexpr explicit no_init_t() = default;
};
/**
* This specialization is for when T is not trivially destructible,
* so the destructor must be called on destruction of `expected'
* Additionally, this requires E to be trivially destructible
*/
template <typename T, typename E, bool = std::is_trivially_destructible_v<T>>
requires std::is_trivially_destructible_v<E>
struct expected_storage_base {
constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {}
constexpr expected_storage_base(no_init_t) : m_has_val{false} {}
template <typename... Args, std::enable_if_t<std::is_constructible_v<T, Args&&...>>* = nullptr>
constexpr expected_storage_base(std::in_place_t, Args&&... args)
: m_val{std::forward<Args>(args)...}, m_has_val{true} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr expected_storage_base(std::in_place_t, std::initializer_list<U> il, Args&&... args)
: m_val{il, std::forward<Args>(args)...}, m_has_val{true} {}
template <typename... Args, std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
: m_unexpect{std::forward<Args>(args)...}, m_has_val{false} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, std::initializer_list<U> il,
Args&&... args)
: m_unexpect{il, std::forward<Args>(args)...}, m_has_val{false} {}
~expected_storage_base() {
if (m_has_val) {
m_val.~T();
}
}
union {
T m_val;
Unexpected<E> m_unexpect;
};
bool m_has_val;
};
/**
* This specialization is for when T is trivially destructible,
* so the destructor of `expected` can be trivial
* Additionally, this requires E to be trivially destructible
*/
template <typename T, typename E>
requires std::is_trivially_destructible_v<E>
struct expected_storage_base<T, E, true> {
constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {}
constexpr expected_storage_base(no_init_t) : m_has_val{false} {}
template <typename... Args, std::enable_if_t<std::is_constructible_v<T, Args&&...>>* = nullptr>
constexpr expected_storage_base(std::in_place_t, Args&&... args)
: m_val{std::forward<Args>(args)...}, m_has_val{true} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr expected_storage_base(std::in_place_t, std::initializer_list<U> il, Args&&... args)
: m_val{il, std::forward<Args>(args)...}, m_has_val{true} {}
template <typename... Args, std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr>
constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
: m_unexpect{std::forward<Args>(args)...}, m_has_val{false} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr explicit expected_storage_base(unexpect_t, std::initializer_list<U> il,
Args&&... args)
: m_unexpect{il, std::forward<Args>(args)...}, m_has_val{false} {}
~expected_storage_base() = default;
union {
T m_val;
Unexpected<E> m_unexpect;
};
bool m_has_val;
};
template <typename T, typename E>
struct expected_operations_base : expected_storage_base<T, E> {
using expected_storage_base<T, E>::expected_storage_base;
template <typename... Args>
void construct(Args&&... args) noexcept {
new (std::addressof(this->m_val)) T{std::forward<Args>(args)...};
this->m_has_val = true;
}
template <typename Rhs>
void construct_with(Rhs&& rhs) noexcept {
new (std::addressof(this->m_val)) T{std::forward<Rhs>(rhs).get()};
this->m_has_val = true;
}
template <typename... Args>
void construct_error(Args&&... args) noexcept {
new (std::addressof(this->m_unexpect)) Unexpected<E>{std::forward<Args>(args)...};
this->m_has_val = false;
}
void assign(const expected_operations_base& rhs) noexcept {
if (!this->m_has_val && rhs.m_has_val) {
geterr().~Unexpected<E>();
construct(rhs.get());
} else {
assign_common(rhs);
}
}
void assign(expected_operations_base&& rhs) noexcept {
if (!this->m_has_val && rhs.m_has_val) {
geterr().~Unexpected<E>();
construct(std::move(rhs).get());
} else {
assign_common(rhs);
}
}
template <typename Rhs>
void assign_common(Rhs&& rhs) {
if (this->m_has_val) {
if (rhs.m_has_val) {
get() = std::forward<Rhs>(rhs).get();
} else {
destroy_val();
construct_error(std::forward<Rhs>(rhs).geterr());
}
} else {
if (!rhs.m_has_val) {
geterr() = std::forward<Rhs>(rhs).geterr();
}
}
}
bool has_value() const {
return this->m_has_val;
}
constexpr T& get() & {
return this->m_val;
}
constexpr const T& get() const& {
return this->m_val;
}
constexpr T&& get() && {
return std::move(this->m_val);
}
constexpr const T&& get() const&& {
return std::move(this->m_val);
}
constexpr Unexpected<E>& geterr() & {
return this->m_unexpect;
}
constexpr const Unexpected<E>& geterr() const& {
return this->m_unexpect;
}
constexpr Unexpected<E>&& geterr() && {
return std::move(this->m_unexpect);
}
constexpr const Unexpected<E>&& geterr() const&& {
return std::move(this->m_unexpect);
}
constexpr void destroy_val() {
get().~T();
}
};
/**
* This manages conditionally having a trivial copy constructor
* This specialization is for when T is trivially copy constructible
* Additionally, this requires E to be trivially copy constructible
*/
template <typename T, typename E, bool = std::is_trivially_copy_constructible_v<T>>
requires std::is_trivially_copy_constructible_v<E>
struct expected_copy_base : expected_operations_base<T, E> {
using expected_operations_base<T, E>::expected_operations_base;
};
/**
* This specialization is for when T is not trivially copy constructible
* Additionally, this requires E to be trivially copy constructible
*/
template <typename T, typename E>
requires std::is_trivially_copy_constructible_v<E>
struct expected_copy_base<T, E, false> : expected_operations_base<T, E> {
using expected_operations_base<T, E>::expected_operations_base;
expected_copy_base() = default;
expected_copy_base(const expected_copy_base& rhs)
: expected_operations_base<T, E>{no_init_t{}} {
if (rhs.has_value()) {
this->construct_with(rhs);
} else {
this->construct_error(rhs.geterr());
}
}
expected_copy_base(expected_copy_base&&) = default;
expected_copy_base& operator=(const expected_copy_base&) = default;
expected_copy_base& operator=(expected_copy_base&&) = default;
};
/**
* This manages conditionally having a trivial move constructor
* This specialization is for when T is trivially move constructible
* Additionally, this requires E to be trivially move constructible
*/
template <typename T, typename E, bool = std::is_trivially_move_constructible_v<T>>
requires std::is_trivially_move_constructible_v<E>
struct expected_move_base : expected_copy_base<T, E> {
using expected_copy_base<T, E>::expected_copy_base;
};
/**
* This specialization is for when T is not trivially move constructible
* Additionally, this requires E to be trivially move constructible
*/
template <typename T, typename E>
requires std::is_trivially_move_constructible_v<E>
struct expected_move_base<T, E, false> : expected_copy_base<T, E> {
using expected_copy_base<T, E>::expected_copy_base;
expected_move_base() = default;
expected_move_base(const expected_move_base&) = default;
expected_move_base(expected_move_base&& rhs) noexcept(std::is_nothrow_move_constructible_v<T>)
: expected_copy_base<T, E>{no_init_t{}} {
if (rhs.has_value()) {
this->construct_with(std::move(rhs));
} else {
this->construct_error(std::move(rhs.geterr()));
}
}
expected_move_base& operator=(const expected_move_base&) = default;
expected_move_base& operator=(expected_move_base&&) = default;
};
/**
* This manages conditionally having a trivial copy assignment operator
* This specialization is for when T is trivially copy assignable
* Additionally, this requires E to be trivially copy assignable
*/
template <typename T, typename E,
bool = std::conjunction_v<std::is_trivially_copy_assignable<T>,
std::is_trivially_copy_constructible<T>,
std::is_trivially_destructible<T>>>
requires std::conjunction_v<std::is_trivially_copy_assignable<E>,
std::is_trivially_copy_constructible<E>,
std::is_trivially_destructible<E>>
struct expected_copy_assign_base : expected_move_base<T, E> {
using expected_move_base<T, E>::expected_move_base;
};
/**
* This specialization is for when T is not trivially copy assignable
* Additionally, this requires E to be trivially copy assignable
*/
template <typename T, typename E>
requires std::conjunction_v<std::is_trivially_copy_assignable<E>,
std::is_trivially_copy_constructible<E>,
std::is_trivially_destructible<E>>
struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {
using expected_move_base<T, E>::expected_move_base;
expected_copy_assign_base() = default;
expected_copy_assign_base(const expected_copy_assign_base&) = default;
expected_copy_assign_base(expected_copy_assign_base&&) = default;
expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) {
this->assign(rhs);
return *this;
}
expected_copy_assign_base& operator=(expected_copy_assign_base&&) = default;
};
/**
* This manages conditionally having a trivial move assignment operator
* This specialization is for when T is trivially move assignable
* Additionally, this requires E to be trivially move assignable
*/
template <typename T, typename E,
bool = std::conjunction_v<std::is_trivially_move_assignable<T>,
std::is_trivially_move_constructible<T>,
std::is_trivially_destructible<T>>>
requires std::conjunction_v<std::is_trivially_move_assignable<E>,
std::is_trivially_move_constructible<E>,
std::is_trivially_destructible<E>>
struct expected_move_assign_base : expected_copy_assign_base<T, E> {
using expected_copy_assign_base<T, E>::expected_copy_assign_base;
};
/**
* This specialization is for when T is not trivially move assignable
* Additionally, this requires E to be trivially move assignable
*/
template <typename T, typename E>
requires std::conjunction_v<std::is_trivially_move_assignable<E>,
std::is_trivially_move_constructible<E>,
std::is_trivially_destructible<E>>
struct expected_move_assign_base<T, E, false> : expected_copy_assign_base<T, E> {
using expected_copy_assign_base<T, E>::expected_copy_assign_base;
expected_move_assign_base() = default;
expected_move_assign_base(const expected_move_assign_base&) = default;
expected_move_assign_base(expected_move_assign_base&&) = default;
expected_move_assign_base& operator=(const expected_move_assign_base&) = default;
expected_move_assign_base& operator=(expected_move_assign_base&& rhs) noexcept(
std::conjunction_v<std::is_nothrow_move_constructible<T>,
std::is_nothrow_move_assignable<T>>) {
this->assign(std::move(rhs));
return *this;
}
};
/**
* expected_delete_ctor_base will conditionally delete copy and move constructors
* depending on whether T is copy/move constructible
* Additionally, this requires E to be copy/move constructible
*/
template <typename T, typename E, bool EnableCopy = std::is_copy_constructible_v<T>,
bool EnableMove = std::is_move_constructible_v<T>>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>>
struct expected_delete_ctor_base {
expected_delete_ctor_base() = default;
expected_delete_ctor_base(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default;
expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>>
struct expected_delete_ctor_base<T, E, true, false> {
expected_delete_ctor_base() = default;
expected_delete_ctor_base(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete;
expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>>
struct expected_delete_ctor_base<T, E, false, true> {
expected_delete_ctor_base() = default;
expected_delete_ctor_base(const expected_delete_ctor_base&) = delete;
expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default;
expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>>
struct expected_delete_ctor_base<T, E, false, false> {
expected_delete_ctor_base() = default;
expected_delete_ctor_base(const expected_delete_ctor_base&) = delete;
expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete;
expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default;
expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default;
};
/**
* expected_delete_assign_base will conditionally delete copy and move assignment operators
* depending on whether T is copy/move constructible + assignable
* Additionally, this requires E to be copy/move constructible + assignable
*/
template <
typename T, typename E,
bool EnableCopy = std::conjunction_v<std::is_copy_constructible<T>, std::is_copy_assignable<T>>,
bool EnableMove = std::conjunction_v<std::is_move_constructible<T>, std::is_move_assignable<T>>>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>,
std::is_copy_assignable<E>, std::is_move_assignable<E>>
struct expected_delete_assign_base {
expected_delete_assign_base() = default;
expected_delete_assign_base(const expected_delete_assign_base&) = default;
expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default;
expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>,
std::is_copy_assignable<E>, std::is_move_assignable<E>>
struct expected_delete_assign_base<T, E, true, false> {
expected_delete_assign_base() = default;
expected_delete_assign_base(const expected_delete_assign_base&) = default;
expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default;
expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>,
std::is_copy_assignable<E>, std::is_move_assignable<E>>
struct expected_delete_assign_base<T, E, false, true> {
expected_delete_assign_base() = default;
expected_delete_assign_base(const expected_delete_assign_base&) = default;
expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete;
expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default;
};
template <typename T, typename E>
requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>,
std::is_copy_assignable<E>, std::is_move_assignable<E>>
struct expected_delete_assign_base<T, E, false, false> {
expected_delete_assign_base() = default;
expected_delete_assign_base(const expected_delete_assign_base&) = default;
expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete;
expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete;
};
/**
* This is needed to be able to construct the expected_default_ctor_base which follows,
* while still conditionally deleting the default constructor.
*/
struct default_constructor_tag {
constexpr explicit default_constructor_tag() = default;
};
/**
* expected_default_ctor_base will ensure that expected
* has a deleted default constructor if T is not default constructible
* This specialization is for when T is default constructible
*/
template <typename T, typename E, bool Enable = std::is_default_constructible_v<T>>
struct expected_default_ctor_base {
constexpr expected_default_ctor_base() noexcept = default;
constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default;
constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default;
expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default;
expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default;
constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
};
template <typename T, typename E>
struct expected_default_ctor_base<T, E, false> {
constexpr expected_default_ctor_base() noexcept = delete;
constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default;
constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default;
expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default;
expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default;
constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
};
template <typename T, typename E, typename U>
using expected_enable_forward_value =
std::enable_if_t<std::is_constructible_v<T, U&&> &&
!std::is_same_v<std::remove_cvref_t<U>, std::in_place_t> &&
!std::is_same_v<Expected<T, E>, std::remove_cvref_t<U>> &&
!std::is_same_v<Unexpected<E>, std::remove_cvref_t<U>>>;
template <typename T, typename E, typename U, typename G, typename UR, typename GR>
using expected_enable_from_other = std::enable_if_t<
std::is_constructible_v<T, UR> && std::is_constructible_v<E, GR> &&
!std::is_constructible_v<T, Expected<U, G>&> && !std::is_constructible_v<T, Expected<U, G>&&> &&
!std::is_constructible_v<T, const Expected<U, G>&> &&
!std::is_constructible_v<T, const Expected<U, G>&&> &&
!std::is_convertible_v<Expected<U, G>&, T> && !std::is_convertible_v<Expected<U, G>&&, T> &&
!std::is_convertible_v<const Expected<U, G>&, T> &&
!std::is_convertible_v<const Expected<U, G>&&, T>>;
} // namespace detail
template <typename T, typename E>
class Expected : private detail::expected_move_assign_base<T, E>,
private detail::expected_delete_ctor_base<T, E>,
private detail::expected_delete_assign_base<T, E>,
private detail::expected_default_ctor_base<T, E> {
public:
using value_type = T;
using error_type = E;
using unexpected_type = Unexpected<E>;
constexpr Expected() = default;
constexpr Expected(const Expected&) = default;
constexpr Expected(Expected&&) = default;
Expected& operator=(const Expected&) = default;
Expected& operator=(Expected&&) = default;
template <typename... Args, std::enable_if_t<std::is_constructible_v<T, Args&&...>>* = nullptr>
constexpr Expected(std::in_place_t, Args&&... args)
: impl_base{std::in_place, std::forward<Args>(args)...},
ctor_base{detail::default_constructor_tag{}} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr Expected(std::in_place_t, std::initializer_list<U> il, Args&&... args)
: impl_base{std::in_place, il, std::forward<Args>(args)...},
ctor_base{detail::default_constructor_tag{}} {}
template <typename G = E, std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr,
std::enable_if_t<!std::is_convertible_v<const G&, E>>* = nullptr>
constexpr explicit Expected(const Unexpected<G>& e)
: impl_base{unexpect_t{}, e.value()}, ctor_base{detail::default_constructor_tag{}} {}
template <typename G = E, std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr,
std::enable_if_t<std::is_convertible_v<const G&, E>>* = nullptr>
constexpr Expected(Unexpected<G> const& e)
: impl_base{unexpect_t{}, e.value()}, ctor_base{detail::default_constructor_tag{}} {}
template <typename G = E, std::enable_if_t<std::is_constructible_v<E, G&&>>* = nullptr,
std::enable_if_t<!std::is_convertible_v<G&&, E>>* = nullptr>
constexpr explicit Expected(Unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G&&>)
: impl_base{unexpect_t{}, std::move(e.value())}, ctor_base{
detail::default_constructor_tag{}} {}
template <typename G = E, std::enable_if_t<std::is_constructible_v<E, G&&>>* = nullptr,
std::enable_if_t<std::is_convertible_v<G&&, E>>* = nullptr>
constexpr Expected(Unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G&&>)
: impl_base{unexpect_t{}, std::move(e.value())}, ctor_base{
detail::default_constructor_tag{}} {}
template <typename... Args, std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr>
constexpr explicit Expected(unexpect_t, Args&&... args)
: impl_base{unexpect_t{}, std::forward<Args>(args)...},
ctor_base{detail::default_constructor_tag{}} {}
template <typename U, typename... Args,
std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, Args&&...>>* =
nullptr>
constexpr explicit Expected(unexpect_t, std::initializer_list<U> il, Args&&... args)
: impl_base{unexpect_t{}, il, std::forward<Args>(args)...},
ctor_base{detail::default_constructor_tag{}} {}
template <typename U, typename G,
std::enable_if_t<!(std::is_convertible_v<U const&, T> &&
std::is_convertible_v<G const&, E>)>* = nullptr,
detail::expected_enable_from_other<T, E, U, G, const U&, const G&>* = nullptr>
constexpr explicit Expected(const Expected<U, G>& rhs)
: ctor_base{detail::default_constructor_tag{}} {
if (rhs.has_value()) {
this->construct(*rhs);
} else {
this->construct_error(rhs.error());
}
}
template <typename U, typename G,
std::enable_if_t<(std::is_convertible_v<U const&, T> &&
std::is_convertible_v<G const&, E>)>* = nullptr,
detail::expected_enable_from_other<T, E, U, G, const U&, const G&>* = nullptr>
constexpr Expected(const Expected<U, G>& rhs) : ctor_base{detail::default_constructor_tag{}} {
if (rhs.has_value()) {
this->construct(*rhs);
} else {
this->construct_error(rhs.error());
}
}
template <typename U, typename G,
std::enable_if_t<!(std::is_convertible_v<U&&, T> && std::is_convertible_v<G&&, E>)>* =
nullptr,
detail::expected_enable_from_other<T, E, U, G, U&&, G&&>* = nullptr>
constexpr explicit Expected(Expected<U, G>&& rhs)
: ctor_base{detail::default_constructor_tag{}} {
if (rhs.has_value()) {
this->construct(std::move(*rhs));
} else {
this->construct_error(std::move(rhs.error()));
}
}
template <typename U, typename G,
std::enable_if_t<(std::is_convertible_v<U&&, T> && std::is_convertible_v<G&&, E>)>* =
nullptr,
detail::expected_enable_from_other<T, E, U, G, U&&, G&&>* = nullptr>
constexpr Expected(Expected<U, G>&& rhs) : ctor_base{detail::default_constructor_tag{}} {
if (rhs.has_value()) {
this->construct(std::move(*rhs));
} else {
this->construct_error(std::move(rhs.error()));
}
}
template <typename U = T, std::enable_if_t<!std::is_convertible_v<U&&, T>>* = nullptr,
detail::expected_enable_forward_value<T, E, U>* = nullptr>
constexpr explicit Expected(U&& v) : Expected{std::in_place, std::forward<U>(v)} {}
template <typename U = T, std::enable_if_t<std::is_convertible_v<U&&, T>>* = nullptr,
detail::expected_enable_forward_value<T, E, U>* = nullptr>
constexpr Expected(U&& v) : Expected{std::in_place, std::forward<U>(v)} {}
template <typename U = T, typename G = T,
std::enable_if_t<std::is_nothrow_constructible_v<T, U&&>>* = nullptr,
std::enable_if_t<(
!std::is_same_v<Expected<T, E>, std::remove_cvref_t<U>> &&
!std::conjunction_v<std::is_scalar<T>, std::is_same<T, std::remove_cvref_t<U>>> &&
std::is_constructible_v<T, U> && std::is_assignable_v<G&, U> &&
std::is_nothrow_move_constructible_v<E>)>* = nullptr>
Expected& operator=(U&& v) {
if (has_value()) {
val() = std::forward<U>(v);
} else {
err().~Unexpected<E>();
new (valptr()) T{std::forward<U>(v)};
this->m_has_val = true;
}
return *this;
}
template <typename U = T, typename G = T,
std::enable_if_t<!std::is_nothrow_constructible_v<T, U&&>>* = nullptr,
std::enable_if_t<(
!std::is_same_v<Expected<T, E>, std::remove_cvref_t<U>> &&
!std::conjunction_v<std::is_scalar<T>, std::is_same<T, std::remove_cvref_t<U>>> &&
std::is_constructible_v<T, U> && std::is_assignable_v<G&, U> &&
std::is_nothrow_move_constructible_v<E>)>* = nullptr>
Expected& operator=(U&& v) {
if (has_value()) {
val() = std::forward<U>(v);
} else {
auto tmp = std::move(err());
err().~Unexpected<E>();
new (valptr()) T{std::forward<U>(v)};
this->m_has_val = true;
}
return *this;
}
template <typename G = E, std::enable_if_t<std::is_nothrow_copy_constructible_v<G> &&
std::is_assignable_v<G&, G>>* = nullptr>
Expected& operator=(const Unexpected<G>& rhs) {
if (!has_value()) {
err() = rhs;
} else {
this->destroy_val();
new (errptr()) Unexpected<E>{rhs};
this->m_has_val = false;
}
return *this;
}
template <typename G = E, std::enable_if_t<std::is_nothrow_move_constructible_v<G> &&
std::is_move_assignable_v<G>>* = nullptr>
Expected& operator=(Unexpected<G>&& rhs) noexcept {
if (!has_value()) {
err() = std::move(rhs);
} else {
this->destroy_val();
new (errptr()) Unexpected<E>{std::move(rhs)};
this->m_has_val = false;
}
return *this;
}
template <typename... Args,
std::enable_if_t<std::is_nothrow_constructible_v<T, Args&&...>>* = nullptr>
void emplace(Args&&... args) {
if (has_value()) {
val() = T{std::forward<Args>(args)...};
} else {
err().~Unexpected<E>();
new (valptr()) T{std::forward<Args>(args)...};
this->m_has_val = true;
}
}
template <typename... Args,
std::enable_if_t<!std::is_nothrow_constructible_v<T, Args&&...>>* = nullptr>
void emplace(Args&&... args) {
if (has_value()) {
val() = T{std::forward<Args>(args)...};
} else {
auto tmp = std::move(err());
err().~Unexpected<E>();
new (valptr()) T{std::forward<Args>(args)...};
this->m_has_val = true;
}
}
template <typename U, typename... Args,
std::enable_if_t<std::is_nothrow_constructible_v<T, std::initializer_list<U>&,
Args&&...>>* = nullptr>
void emplace(std::initializer_list<U> il, Args&&... args) {
if (has_value()) {
T t{il, std::forward<Args>(args)...};
val() = std::move(t);
} else {
err().~Unexpected<E>();
new (valptr()) T{il, std::forward<Args>(args)...};
this->m_has_val = true;
}
}
template <typename U, typename... Args,
std::enable_if_t<!std::is_nothrow_constructible_v<T, std::initializer_list<U>&,
Args&&...>>* = nullptr>
void emplace(std::initializer_list<U> il, Args&&... args) {
if (has_value()) {
T t{il, std::forward<Args>(args)...};
val() = std::move(t);
} else {
auto tmp = std::move(err());
err().~Unexpected<E>();
new (valptr()) T{il, std::forward<Args>(args)...};
this->m_has_val = true;
}
}
constexpr T* operator->() {
return valptr();
}
constexpr const T* operator->() const {
return valptr();
}
template <typename U = T>
constexpr U& operator*() & {
return val();
}
template <typename U = T>
constexpr const U& operator*() const& {
return val();
}
template <typename U = T>
constexpr U&& operator*() && {
return std::move(val());
}
template <typename U = T>
constexpr const U&& operator*() const&& {
return std::move(val());
}
constexpr bool has_value() const noexcept {
return this->m_has_val;
}
constexpr explicit operator bool() const noexcept {
return this->m_has_val;
}
template <typename U = T>
constexpr U& value() & {
return val();
}
template <typename U = T>
constexpr const U& value() const& {
return val();
}
template <typename U = T>
constexpr U&& value() && {
return std::move(val());
}
template <typename U = T>
constexpr const U&& value() const&& {
return std::move(val());
}
constexpr E& error() & {
return err().value();
}
constexpr const E& error() const& {
return err().value();
}
constexpr E&& error() && {
return std::move(err().value());
}
constexpr const E&& error() const&& {
return std::move(err().value());
}
template <typename U>
constexpr T value_or(U&& v) const& {
static_assert(std::is_copy_constructible_v<T> && std::is_convertible_v<U&&, T>,
"T must be copy-constructible and convertible from U&&");
return bool(*this) ? **this : static_cast<T>(std::forward<U>(v));
}
template <typename U>
constexpr T value_or(U&& v) && {
static_assert(std::is_move_constructible_v<T> && std::is_convertible_v<U&&, T>,
"T must be move-constructible and convertible from U&&");
return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v));
}
private:
static_assert(!std::is_reference_v<T>, "T must not be a reference");
static_assert(!std::is_same_v<T, std::remove_cv_t<std::in_place_t>>,
"T must not be std::in_place_t");
static_assert(!std::is_same_v<T, std::remove_cv_t<unexpect_t>>, "T must not be unexpect_t");
static_assert(!std::is_same_v<T, std::remove_cv_t<Unexpected<E>>>,
"T must not be Unexpected<E>");
static_assert(!std::is_reference_v<E>, "E must not be a reference");
T* valptr() {
return std::addressof(this->m_val);
}
const T* valptr() const {
return std::addressof(this->m_val);
}
Unexpected<E>* errptr() {
return std::addressof(this->m_unexpect);
}
const Unexpected<E>* errptr() const {
return std::addressof(this->m_unexpect);
}
template <typename U = T>
constexpr U& val() {
return this->m_val;
}
template <typename U = T>
constexpr const U& val() const {
return this->m_val;
}
constexpr Unexpected<E>& err() {
return this->m_unexpect;
}
constexpr const Unexpected<E>& err() const {
return this->m_unexpect;
}
using impl_base = detail::expected_move_assign_base<T, E>;
using ctor_base = detail::expected_default_ctor_base<T, E>;
};
template <typename T, typename E, typename U, typename F>
constexpr bool operator==(const Expected<T, E>& lhs, const Expected<U, F>& rhs) {
return (lhs.has_value() != rhs.has_value())
? false
: (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs);
}
template <typename T, typename E, typename U, typename F>
constexpr bool operator!=(const Expected<T, E>& lhs, const Expected<U, F>& rhs) {
return !operator==(lhs, rhs);
}
template <typename T, typename E, typename U>
constexpr bool operator==(const Expected<T, E>& x, const U& v) {
return x.has_value() ? *x == v : false;
}
template <typename T, typename E, typename U>
constexpr bool operator==(const U& v, const Expected<T, E>& x) {
return x.has_value() ? *x == v : false;
}
template <typename T, typename E, typename U>
constexpr bool operator!=(const Expected<T, E>& x, const U& v) {
return !operator==(x, v);
}
template <typename T, typename E, typename U>
constexpr bool operator!=(const U& v, const Expected<T, E>& x) {
return !operator==(v, x);
}
template <typename T, typename E>
constexpr bool operator==(const Expected<T, E>& x, const Unexpected<E>& e) {
return x.has_value() ? false : x.error() == e.value();
}
template <typename T, typename E>
constexpr bool operator==(const Unexpected<E>& e, const Expected<T, E>& x) {
return x.has_value() ? false : x.error() == e.value();
}
template <typename T, typename E>
constexpr bool operator!=(const Expected<T, E>& x, const Unexpected<E>& e) {
return !operator==(x, e);
}
template <typename T, typename E>
constexpr bool operator!=(const Unexpected<E>& e, const Expected<T, E>& x) {
return !operator==(e, x);
}
} // namespace Common
+10 -1
View File
@@ -54,7 +54,6 @@ SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true); SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(GpuLogLevel, true);
SWITCHABLE(Language, true); SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true); SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false); SWITCHABLE(NvdecEmulation, false);
@@ -213,6 +212,16 @@ bool IsNceEnabled() {
return is_nce_enabled; return is_nce_enabled;
} }
static u64 current_program_id = 0;
void SetCurrentProgramID(u64 program_id) {
current_program_id = program_id;
}
u64 GetCurrentProgramID() {
return current_program_id;
}
bool IsDockedMode() { bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
} }
+14 -5
View File
@@ -573,6 +573,13 @@ struct Values {
false, false,
#endif #endif
"rescale_hack", Category::RendererHacks}; "rescale_hack", Category::RendererHacks};
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
false,
"enable_gpu_buffer_readback",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks}; Category::RendererHacks};
@@ -788,8 +795,8 @@ struct Values {
false}; // runtime_modifiable_ — startup-only false}; // runtime_modifiable_ — startup-only
Setting<bool> dump_exefs{linkage, false, "dump_exefs", Category::Debugging}; Setting<bool> dump_exefs{linkage, false, "dump_exefs", Category::Debugging};
Setting<bool> dump_nso{linkage, false, "dump_nso", Category::Debugging}; Setting<bool> dump_nso{linkage, false, "dump_nso", Category::Debugging};
Setting<bool> dump_shaders{ Setting<bool> dump_guest_shaders{
linkage, false, "dump_shaders", Category::DebuggingGraphics, Specialization::Default, linkage, false, "dump_guest_shaders", Category::DebuggingGraphics, Specialization::Default,
false}; false};
Setting<bool> dump_macros{ Setting<bool> dump_macros{
linkage, false, "dump_macros", Category::DebuggingGraphics, Specialization::Default, false}; linkage, false, "dump_macros", Category::DebuggingGraphics, Specialization::Default, false};
@@ -813,9 +820,8 @@ struct Values {
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging}; Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
// GPU Logging // GPU Logging
Setting<bool> gpu_logging_enabled{linkage, false, "gpu_logging_enabled", Category::Debugging}; Setting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Off, "gpu_log_level",
SwitchableSetting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level", Category::Debugging};
Category::Debugging};
Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging}; Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging};
Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging}; Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging};
Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking", Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking",
@@ -876,6 +882,9 @@ bool IsFastmemEnabled();
void SetNceEnabled(bool is_64bit); void SetNceEnabled(bool is_64bit);
bool IsNceEnabled(); bool IsNceEnabled();
void SetCurrentProgramID(u64 program_id);
u64 GetCurrentProgramID();
bool IsOpenGL(); bool IsOpenGL();
bool IsDockedMode(); bool IsDockedMode();
+4 -1
View File
@@ -326,6 +326,9 @@ struct System::Impl {
LOG_INFO(Core, "Loading {} ({:016X}) ...", name, params.program_id); LOG_INFO(Core, "Loading {} ({:016X}) ...", name, params.program_id);
// Expose program id to dump sites and other global readers.
Settings::SetCurrentProgramID(params.program_id);
// Track launch time for frontend launches // Track launch time for frontend launches
LaunchTimestampCache::SaveLaunchTimestamp(params.program_id); LaunchTimestampCache::SaveLaunchTimestamp(params.program_id);
@@ -347,7 +350,7 @@ struct System::Impl {
// Register with applet manager // Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear // All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::make_unique<Service::Process>(*std::move(process)), params); applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
if (Settings::values.gamecard_inserted) { if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) { if (Settings::values.gamecard_current_game) {
-1
View File
@@ -10,7 +10,6 @@
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/expected.h"
// All the constants in this file come from <https://switchbrew.org/wiki/Error_codes> // All the constants in this file come from <https://switchbrew.org/wiki/Error_codes>
+6 -6
View File
@@ -729,7 +729,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface(ensure_token_id); rb.PushIpcInterface(ctx, ensure_token_id);
} }
void LoadIdTokenCacheDeprecated(HLERequestContext& ctx) { void LoadIdTokenCacheDeprecated(HLERequestContext& ctx) {
@@ -921,7 +921,7 @@ void Module::Interface::GetProfile(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IProfile>(system, user_id, *profile_manager); rb.PushIpcInterface<IProfile>(ctx, system, user_id, *profile_manager);
} }
void Module::Interface::IsUserRegistrationRequestPermitted(HLERequestContext& ctx) { void Module::Interface::IsUserRegistrationRequestPermitted(HLERequestContext& ctx) {
@@ -993,7 +993,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(HLERequestContext& c
LOG_DEBUG(Service_ACC, "called"); LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IManagerForApplication>(system, profile_manager); rb.PushIpcInterface<IManagerForApplication>(ctx, system, profile_manager);
} }
void Module::Interface::IsUserAccountSwitchLocked(HLERequestContext& ctx) { void Module::Interface::IsUserAccountSwitchLocked(HLERequestContext& ctx) {
@@ -1089,7 +1089,7 @@ void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IProfileEditor>(system, user_id, *profile_manager); rb.PushIpcInterface<IProfileEditor>(ctx, system, user_id, *profile_manager);
} }
void Module::Interface::GetBaasAccountAdministrator(HLERequestContext &ctx) { void Module::Interface::GetBaasAccountAdministrator(HLERequestContext &ctx) {
@@ -1100,7 +1100,7 @@ void Module::Interface::GetBaasAccountAdministrator(HLERequestContext &ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IAdministrator>(system, uuid); rb.PushIpcInterface<IAdministrator>(ctx, system, uuid);
} }
void Module::Interface::ListQualifiedUsers(HLERequestContext& ctx) { void Module::Interface::ListQualifiedUsers(HLERequestContext& ctx) {
@@ -1143,7 +1143,7 @@ void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext&
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IManagerForSystemService>(system, uuid); rb.PushIpcInterface<IManagerForSystemService>(ctx, system, uuid);
} }
void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
+1 -1
View File
@@ -35,7 +35,7 @@ void IAsyncContext::GetSystemEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(completion_event->GetReadableEvent()); rb.PushCopyObjects(ctx, completion_event->GetReadableEvent());
} }
void IAsyncContext::Cancel(HLERequestContext& ctx) { void IAsyncContext::Cancel(HLERequestContext& ctx) {
+4
View File
@@ -109,6 +109,10 @@ struct Applet {
std::list<std::shared_ptr<Applet>> child_applets{}; std::list<std::shared_ptr<Applet>> child_applets{};
bool is_completed{}; bool is_completed{};
std::shared_ptr<Applet> reserved_applet{};
bool unwind_after_reserved{};
bool is_winding{};
// Self state // Self state
bool exit_locked{}; bool exit_locked{};
s32 fatal_section_count{}; s32 fatal_section_count{};
@@ -25,6 +25,13 @@ void AppletStorageChannel::Push(Kernel::KernelCore& kernel, std::shared_ptr<ISto
m_event.Signal(kernel); m_event.Signal(kernel);
} }
void AppletStorageChannel::Unpop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage> storage) {
std::scoped_lock lk{m_lock};
m_data.emplace_front(std::move(storage));
m_event.Signal(kernel);
}
Result AppletStorageChannel::Pop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage>* out_storage) { Result AppletStorageChannel::Pop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage>* out_storage) {
std::scoped_lock lk{m_lock}; std::scoped_lock lk{m_lock};
@@ -26,6 +26,7 @@ public:
~AppletStorageChannel(); ~AppletStorageChannel();
void Push(Kernel::KernelCore& kernel, std::shared_ptr<IStorage> storage); void Push(Kernel::KernelCore& kernel, std::shared_ptr<IStorage> storage);
void Unpop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage> storage);
Result Pop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage>* out_storage); Result Pop(Kernel::KernelCore& kernel, std::shared_ptr<IStorage>* out_storage);
Kernel::KReadableEvent* GetEvent(); Kernel::KReadableEvent* GetEvent();
+1 -1
View File
@@ -267,7 +267,7 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) {
if (Settings::values.enable_overlay && m_window_system->GetOverlayDisplayApplet() == nullptr) { if (Settings::values.enable_overlay && m_window_system->GetOverlayDisplayApplet() == nullptr) {
if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) { if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) {
auto overlay_applet = std::make_shared<Applet>(m_system, std::make_unique<Service::Process>(*std::move(overlay_process)), false); auto overlay_applet = std::make_shared<Applet>(m_system, std::move(overlay_process), false);
overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay); overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay);
overlay_applet->applet_id = AppletId::OverlayDisplay; overlay_applet->applet_id = AppletId::OverlayDisplay;
overlay_applet->type = AppletType::OverlayApplet; overlay_applet->type = AppletType::OverlayApplet;
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "applet_web_browser_types.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/file.h" #include "common/fs/file.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@@ -370,16 +371,13 @@ void WebBrowser::ExtractOfflineRomFS() {
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
const bool use_tlv_output = const bool use_tlv_output =
(web_arg_header.shim_kind == ShimKind::Share && (web_arg_header.shim_kind == ShimKind::Share && web_applet_version >= WebAppletVersion::Version196608)
web_applet_version >= WebAppletVersion::Version196608) || || (web_arg_header.shim_kind == ShimKind::Web && web_applet_version >= WebAppletVersion::Version524288)
(web_arg_header.shim_kind == ShimKind::Web && || (web_arg_header.shim_kind == ShimKind::Lhub);
web_applet_version >= WebAppletVersion::Version524288) ||
(web_arg_header.shim_kind == ShimKind::Lhub);
// https://switchbrew.org/wiki/Internet_Browser#TLVs // https://switchbrew.org/wiki/Internet_Browser#TLVs
if (use_tlv_output) { if (use_tlv_output) {
LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", exit_reason, last_url, last_url.size());
exit_reason, last_url, last_url.size());
// storage size for TLVs is 0x2000 bytes (as per switchbrew documentation) // storage size for TLVs is 0x2000 bytes (as per switchbrew documentation)
constexpr size_t TLV_STORAGE_SIZE = 0x2000; constexpr size_t TLV_STORAGE_SIZE = 0x2000;
@@ -600,11 +598,14 @@ void WebBrowser::ExecuteShare() {
void WebBrowser::ExecuteWeb() { void WebBrowser::ExecuteWeb() {
LOG_INFO(Service_AM, "Opening external URL at {}", external_url); LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
frontend.OpenExternalWebPage(external_url, [this](WebExitReason exit_reason, std::string last_url) {
frontend.OpenExternalWebPage(external_url, // Offline and web applets must be explicitly exited from because they respect exit state
[this](WebExitReason exit_reason, std::string last_url) { // Unlike the other web stuffs
WebBrowserExit(exit_reason, last_url); if (exit_reason == WebExitReason::ExitRequested || exit_reason == WebExitReason::EndButtonPressed)
}); exit_reason = (web_arg_header.shim_kind == ShimKind::Web || web_arg_header.shim_kind == ShimKind::Offline)
? WebExitReason::ExitRequested : WebExitReason::EndButtonPressed;
WebBrowserExit(exit_reason, last_url);
});
} }
void WebBrowser::ExecuteWifi() { void WebBrowser::ExecuteWifi() {
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,7 +15,10 @@ namespace Service::AM {
HidRegistration::HidRegistration(Core::System& system, Process& process) : m_process(process) { HidRegistration::HidRegistration(Core::System& system, Process& process) : m_process(process) {
m_hid_server = system.ServiceManager().GetService<HID::IHidServer>("hid", true); m_hid_server = system.ServiceManager().GetService<HID::IHidServer>("hid", true);
this->RegisterCurrentProcess();
}
void HidRegistration::RegisterCurrentProcess() {
if (m_process.IsInitialized()) { if (m_process.IsInitialized()) {
m_hid_server->GetResourceManager()->RegisterAppletResourceUserId(m_process.GetProcessId(), m_hid_server->GetResourceManager()->RegisterAppletResourceUserId(m_process.GetProcessId(),
true); true);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -24,6 +27,7 @@ public:
explicit HidRegistration(Core::System& system, Process& process); explicit HidRegistration(Core::System& system, Process& process);
~HidRegistration(); ~HidRegistration();
void RegisterCurrentProcess();
void EnableAppletToGetInput(bool enable); void EnableAppletToGetInput(bool enable);
private: private:
@@ -171,6 +171,18 @@ void LifecycleManager::SignalSystemEventIfNeeded(Kernel::KernelCore& kernel) {
} }
} }
void LifecycleManager::ResetForRelaunch() {
m_unordered_messages.clear();
m_activity_state = ActivityState::BackgroundVisible;
m_requested_focus_state = FocusState{};
m_acknowledged_focus_state = FocusState{};
m_has_focus_state_changed = true;
m_suspend_mode = SuspendMode::NoOverride;
m_forced_suspend = false;
}
bool LifecycleManager::PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message) { bool LifecycleManager::PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message) {
const auto message = this->PopMessageInOrderOfPriority(); const auto message = this->PopMessageInOrderOfPriority();
this->SignalSystemEventIfNeeded(kernel); this->SignalSystemEventIfNeeded(kernel);
@@ -138,6 +138,8 @@ public:
void PushUnorderedMessage(Kernel::KernelCore& kernel, AppletMessage message); void PushUnorderedMessage(Kernel::KernelCore& kernel, AppletMessage message);
bool PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message); bool PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message);
void ResetForRelaunch();
private: private:
FocusState GetFocusStateWhileForegroundObscured() const; FocusState GetFocusStateWhileForegroundObscured() const;
FocusState GetFocusStateWhileBackground(bool is_obscured) const; FocusState GetFocusStateWhileBackground(bool is_obscured) const;
+24 -8
View File
@@ -40,23 +40,23 @@ namespace {
} }
} }
[[nodiscard]] inline std::optional<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) { [[nodiscard]] inline std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
// Get the appropriate loader to parse this NCA. // Get the appropriate loader to parse this NCA.
out_loader = Loader::GetLoader(system, file, program_id, program_index); out_loader = Loader::GetLoader(system, file, program_id, program_index);
// Ensure we have a loader which can parse the NCA. // Ensure we have a loader which can parse the NCA.
if (out_loader) { if (out_loader) {
// Try to load the process. // Try to load the process.
auto process = std::make_optional<Process>(system); auto process = std::make_unique<Process>(system);
if (process->Initialize(*out_loader, out_load_result)) { if (process->Initialize(*out_loader, out_load_result)) {
return process; return process;
} }
} }
return std::nullopt; return nullptr;
} }
} // Anonymous namespace } // Anonymous namespace
std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) { std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
// Attempt to load program NCA. // Attempt to load program NCA.
FileSys::VirtualFile nca_raw{}; FileSys::VirtualFile nca_raw{};
@@ -66,7 +66,7 @@ std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 mi
// Ensure we retrieved a program NCA. // Ensure we retrieved a program NCA.
if (!nca_raw) { if (!nca_raw) {
return std::nullopt; return nullptr;
} }
// Ensure we have a suitable version. // Ensure we have a suitable version.
@@ -76,7 +76,7 @@ std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 mi
(nca.GetKeyGeneration() < minimum_key_generation || (nca.GetKeyGeneration() < minimum_key_generation ||
nca.GetKeyGeneration() > maximum_key_generation)) { nca.GetKeyGeneration() > maximum_key_generation)) {
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, nca.GetKeyGeneration()); LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, nca.GetKeyGeneration());
return std::nullopt; return nullptr;
} }
} }
@@ -85,7 +85,7 @@ std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 mi
return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0); return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0);
} }
std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) { std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
if (auto process = CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index); process) { if (auto process = CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index); process) {
FileSys::NACP nacp; FileSys::NACP nacp;
if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
@@ -110,7 +110,23 @@ std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, st
system.GetARPManager().Register(launch.title_id, launch, out_control); system.GetARPManager().Register(launch.title_id, launch, out_control);
return process; return process;
} }
return std::nullopt; return nullptr;
}
bool ReinitializeProcess(Core::System& system, Process& process, u64 program_id) {
auto& storage = system.GetContentProviderUnion();
const auto nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
if (!nca_raw) {
return false;
}
auto loader = Loader::GetLoader(system, nca_raw, program_id, 0);
if (!loader) {
return false;
}
Loader::ResultStatus status{};
return process.Initialize(*loader, status);
} }
} // namespace Service::AM } // namespace Service::AM
+4 -2
View File
@@ -27,7 +27,9 @@ class Process;
namespace Service::AM { namespace Service::AM {
std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation); std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index); std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index);
bool ReinitializeProcess(Core::System& system, Process& process, u64 program_id);
} // namespace Service::AM } // namespace Service::AM
@@ -35,9 +35,9 @@ Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_applicati
std::unique_ptr<Loader::AppLoader> loader; std::unique_ptr<Loader::AppLoader> loader;
Loader::ResultStatus result; Loader::ResultStatus result;
auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0); auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0);
R_UNLESS(process != std::nullopt, ResultUnknown); R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true); const auto applet = std::make_shared<Applet>(system, std::move(process), true);
applet->program_id = program_id; applet->program_id = program_id;
applet->applet_id = AppletId::Application; applet->applet_id = AppletId::Application;
applet->type = AppletType::Application; applet->type = AppletType::Application;
@@ -88,9 +88,9 @@ Result IApplicationCreator::CreateSystemApplication(
std::vector<u8> control; std::vector<u8> control;
std::unique_ptr<Loader::AppLoader> loader; std::unique_ptr<Loader::AppLoader> loader;
auto process = CreateProcess(system, application_id, 1, 22); auto process = CreateProcess(system, application_id, 1, 22);
R_UNLESS(process != std::nullopt, ResultUnknown); R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true); const auto applet = std::make_shared<Applet>(system, std::move(process), true);
applet->program_id = application_id; applet->program_id = application_id;
applet->applet_id = AppletId::Starter; applet->applet_id = AppletId::Starter;
applet->type = AppletType::LibraryApplet; applet->type = AppletType::LibraryApplet;
@@ -75,7 +75,7 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_,
{105, D<&ILibraryAppletAccessor::GetPopOutDataEvent>, "GetPopOutDataEvent"}, {105, D<&ILibraryAppletAccessor::GetPopOutDataEvent>, "GetPopOutDataEvent"},
{106, D<&ILibraryAppletAccessor::GetPopInteractiveOutDataEvent>, "GetPopInteractiveOutDataEvent"}, {106, D<&ILibraryAppletAccessor::GetPopInteractiveOutDataEvent>, "GetPopInteractiveOutDataEvent"},
{110, nullptr, "NeedsToExitProcess"}, {110, nullptr, "NeedsToExitProcess"},
{120, nullptr, "GetLibraryAppletInfo"}, {120, D<&ILibraryAppletAccessor::GetLibraryAppletInfo>, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"}, {150, nullptr, "RequestForAppletToGetForeground"},
{160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+ {160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+
{170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+ {170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+
@@ -218,6 +218,16 @@ Result ILibraryAppletAccessor::GetIndirectLayerConsumerHandle(Out<u64> out_handl
R_SUCCEED(); R_SUCCEED();
} }
Result ILibraryAppletAccessor::GetLibraryAppletInfo(
Out<LibraryAppletInfo> out_library_applet_info) {
LOG_INFO(Service_AM, "called");
*out_library_applet_info = {
.applet_id = m_applet->applet_id,
.library_applet_mode = m_applet->library_applet_mode,
};
R_SUCCEED();
}
Result ILibraryAppletAccessor::Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event) { Result ILibraryAppletAccessor::Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");
*out_event = m_applet->unknown_event.GetHandle(); *out_event = m_applet->unknown_event.GetHandle();
@@ -6,6 +6,7 @@
#pragma once #pragma once
#include "core/hle/service/am/service/library_applet_self_accessor.h"
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@@ -21,6 +22,10 @@ public:
std::shared_ptr<Applet> applet); std::shared_ptr<Applet> applet);
~ILibraryAppletAccessor(); ~ILibraryAppletAccessor();
std::shared_ptr<Applet> GetApplet() const {
return m_applet;
}
private: private:
Result GetAppletStateChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result GetAppletStateChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result IsCompleted(Out<bool> out_is_completed); Result IsCompleted(Out<bool> out_is_completed);
@@ -37,6 +42,7 @@ private:
Result GetPopOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result GetPopOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetPopInteractiveOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result GetPopInteractiveOutDataEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetIndirectLayerConsumerHandle(Out<u64> out_handle); Result GetIndirectLayerConsumerHandle(Out<u64> out_handle);
Result GetLibraryAppletInfo(Out<LibraryAppletInfo> out_library_applet_info);
Result Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event); Result Unknown170(OutCopyHandle<Kernel::KReadableEvent> out_event);
void FrontendExecute(); void FrontendExecute();
@@ -123,7 +123,7 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200); auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200);
if (process) { if (process) {
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), false); const auto applet = std::make_shared<Applet>(system, std::move(process), false);
applet->program_id = program_id; applet->program_id = program_id;
applet->applet_id = applet_id; applet->applet_id = applet_id;
applet->type = AppletType::LibraryApplet; applet->type = AppletType::LibraryApplet;
@@ -233,8 +233,9 @@ Result ILibraryAppletSelfAccessor::ReportVisibleErrorWithErrorContext(
R_SUCCEED(); R_SUCCEED();
} }
Result ILibraryAppletSelfAccessor::UnpopInData() { Result ILibraryAppletSelfAccessor::UnpopInData(SharedPointer<IStorage> storage) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_INFO(Service_AM, "called");
m_broker->GetInData().Unpop(system.Kernel(), storage);
R_SUCCEED(); R_SUCCEED();
} }
@@ -72,7 +72,7 @@ private:
Result ReportVisibleError(ErrorCode error_code); Result ReportVisibleError(ErrorCode error_code);
Result ReportVisibleErrorWithErrorContext( Result ReportVisibleErrorWithErrorContext(
ErrorCode error_code, InLargeData<ErrorContext, BufferAttr_HipcMapAlias> error_context); ErrorCode error_code, InLargeData<ErrorContext, BufferAttr_HipcMapAlias> error_context);
Result UnpopInData(); Result UnpopInData(SharedPointer<IStorage> storage);
Result GetMainAppletApplicationDesiredLanguage(Out<u64> out_desired_language); Result GetMainAppletApplicationDesiredLanguage(Out<u64> out_desired_language);
Result GetCurrentApplicationId(Out<u64> out_application_id); Result GetCurrentApplicationId(Out<u64> out_application_id);
Result GetMainAppletAvailableUsers(Out<bool> out_can_select_any_user, Out<s32> out_users_count, Result GetMainAppletAvailableUsers(Out<bool> out_can_select_any_user, Out<s32> out_users_count,
@@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/service/am/applet.h"
#include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/am/service/library_applet_accessor.h" #include "core/hle/service/am/service/library_applet_accessor.h"
#include "core/hle/service/am/service/process_winding_controller.h" #include "core/hle/service/am/service/process_winding_controller.h"
@@ -43,6 +45,18 @@ Result IProcessWindingController::OpenCallingLibraryApplet(
Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet) { Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet) {
LOG_INFO(Service_AM, "called"); LOG_INFO(Service_AM, "called");
std::shared_ptr<Applet> reserved_applet;
{
std::scoped_lock lk{m_applet->lock};
reserved_applet = std::move(m_applet->reserved_applet);
}
if (reserved_applet != nullptr) {
*out_calling_library_applet = std::make_shared<ILibraryAppletAccessor>(
system, reserved_applet->caller_applet_broker, reserved_applet);
R_SUCCEED();
}
const auto caller_applet = m_applet->caller_applet.lock(); const auto caller_applet = m_applet->caller_applet.lock();
if (caller_applet == nullptr) { if (caller_applet == nullptr) {
LOG_ERROR(Service_AM, "No caller applet available"); LOG_ERROR(Service_AM, "No caller applet available");
@@ -74,22 +88,82 @@ Result IProcessWindingController::PopContext(Out<SharedPointer<IStorage>> out_co
} }
Result IProcessWindingController::CancelWindingReservation() { Result IProcessWindingController::CancelWindingReservation() {
LOG_WARNING(Service_AM, "STUBBED"); LOG_INFO(Service_AM, "called");
std::scoped_lock lk{m_applet->lock};
m_applet->reserved_applet.reset();
m_applet->unwind_after_reserved = false;
R_SUCCEED(); R_SUCCEED();
} }
Result IProcessWindingController::WindAndDoReserved() { Result IProcessWindingController::WindAndDoReserved() {
LOG_WARNING(Service_AM, "STUBBED"); LOG_INFO(Service_AM, "called");
std::shared_ptr<Applet> reserved_applet;
{
std::scoped_lock lk{m_applet->lock};
reserved_applet = m_applet->reserved_applet;
m_applet->display_layer_manager.SetWindowVisibility(false);
m_applet->exit_locked = false;
system.SetExitLocked(false);
}
if (reserved_applet) {
{
std::scoped_lock lk{m_applet->lock};
m_applet->is_winding = true;
}
{
std::scoped_lock lk{reserved_applet->lock};
reserved_applet->window_visible = true;
reserved_applet->process->Run();
}
if (reserved_applet->frontend) {
reserved_applet->frontend->Initialize();
reserved_applet->frontend->Execute();
}
} else {
LOG_WARNING(Service_AM, "called without a reserved applet to start");
}
m_applet->process->Terminate();
R_SUCCEED(); R_SUCCEED();
} }
Result IProcessWindingController::ReserveToStartAndWaitAndUnwindThis() { Result IProcessWindingController::ReserveToStartAndWaitAndUnwindThis(
LOG_WARNING(Service_AM, "STUBBED"); SharedPointer<ILibraryAppletAccessor> reserved_applet_accessor) {
LOG_INFO(Service_AM, "called");
if (reserved_applet_accessor == nullptr) {
LOG_ERROR(Service_AM, "No applet accessor provided");
R_THROW(ResultUnknown);
}
std::scoped_lock lk{m_applet->lock};
m_applet->reserved_applet = reserved_applet_accessor->GetApplet();
m_applet->unwind_after_reserved = true;
R_SUCCEED(); R_SUCCEED();
} }
Result IProcessWindingController::ReserveToStartAndWait() { Result IProcessWindingController::ReserveToStartAndWait(
LOG_WARNING(Service_AM, "STUBBED"); SharedPointer<ILibraryAppletAccessor> reserved_applet_accessor) {
LOG_INFO(Service_AM, "called");
if (reserved_applet_accessor == nullptr) {
LOG_ERROR(Service_AM, "No applet accessor provided");
R_THROW(ResultUnknown);
}
std::scoped_lock lk{m_applet->lock};
m_applet->reserved_applet = reserved_applet_accessor->GetApplet();
m_applet->unwind_after_reserved = false;
R_SUCCEED(); R_SUCCEED();
} }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -29,8 +29,9 @@ private:
Result PopContext(Out<SharedPointer<IStorage>> out_context); Result PopContext(Out<SharedPointer<IStorage>> out_context);
Result CancelWindingReservation(); Result CancelWindingReservation();
Result WindAndDoReserved(); Result WindAndDoReserved();
Result ReserveToStartAndWaitAndUnwindThis(); Result ReserveToStartAndWaitAndUnwindThis(
Result ReserveToStartAndWait(); SharedPointer<ILibraryAppletAccessor> reserved_applet_accessor);
Result ReserveToStartAndWait(SharedPointer<ILibraryAppletAccessor> reserved_applet_accessor);
const std::shared_ptr<Applet> m_applet; const std::shared_ptr<Applet> m_applet;
}; };
+57
View File
@@ -9,6 +9,7 @@
#include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet.h"
#include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/event_observer.h" #include "core/hle/service/am/event_observer.h"
#include "core/hle/service/am/process_creation.h"
#include "core/hle/service/am/window_system.h" #include "core/hle/service/am/window_system.h"
namespace Service::AM { namespace Service::AM {
@@ -240,6 +241,37 @@ void WindowSystem::PruneTerminatedAppletsLocked() {
continue; continue;
} }
// A winding applet has had its own process killed but is kept alive as a transparent slot
// while the reserved applet running in its place finishes (see WindAndDoReserved()).
if (applet->is_winding) {
if (!applet->child_applets.empty()) {
it = std::next(it);
continue;
}
const bool unwind = applet->unwind_after_reserved;
applet->is_winding = false;
applet->unwind_after_reserved = false;
if (unwind && this->RestartAppletProcessLocked(applet.get())) {
const u64 new_aruid = applet->aruid.pid;
const auto next = std::next(it);
if (new_aruid != aruid) {
auto node = m_applets.extract(it);
node.key() = new_aruid;
m_applets.insert(std::move(node));
}
applet->process->Run();
m_event_observer->RequestUpdate();
it = next;
continue;
}
applet->reserved_applet.reset();
}
// Terminated, so ensure all child applets are terminated. // Terminated, so ensure all child applets are terminated.
if (!applet->child_applets.empty()) { if (!applet->child_applets.empty()) {
this->TerminateChildAppletsLocked(applet.get()); this->TerminateChildAppletsLocked(applet.get());
@@ -301,6 +333,28 @@ void WindowSystem::PruneTerminatedAppletsLocked() {
} }
} }
bool WindowSystem::RestartAppletProcessLocked(Applet* applet) {
if (!ReinitializeProcess(m_system, *applet->process, applet->program_id)) {
LOG_ERROR(Service_AM, "Failed to restart winding applet_id={}",
static_cast<u32>(applet->applet_id));
return false;
}
applet->aruid.pid = applet->process->GetProcessId();
applet->is_process_running = false;
applet->is_completed = false;
applet->hid_registration.RegisterCurrentProcess();
applet->lifecycle_manager.ResetForRelaunch();
applet->is_activity_runnable = false;
applet->launch_reason.flag = 1;
m_event_observer->TrackAppletProcess(*applet);
return true;
}
bool WindowSystem::LockHomeMenuIntoForegroundLocked() { bool WindowSystem::LockHomeMenuIntoForegroundLocked() {
// If the home menu is not locked into foreground, then there's nothing to do. // If the home menu is not locked into foreground, then there's nothing to do.
if (m_home_menu == nullptr || !m_home_menu_foreground_locked) { if (m_home_menu == nullptr || !m_home_menu_foreground_locked) {
@@ -352,6 +406,9 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
const bool has_obscuring_child_applets = [&] { const bool has_obscuring_child_applets = [&] {
for (const auto& child_applet : applet->child_applets) { for (const auto& child_applet : applet->child_applets) {
std::scoped_lock lk2{child_applet->lock}; std::scoped_lock lk2{child_applet->lock};
if (child_applet->is_winding) {
return true;
}
const auto mode = child_applet->library_applet_mode; const auto mode = child_applet->library_applet_mode;
if (child_applet->is_process_running && child_applet->window_visible && if (child_applet->is_process_running && child_applet->window_visible &&
(mode == LibraryAppletMode::AllForeground || (mode == LibraryAppletMode::AllForeground ||
+1
View File
@@ -61,6 +61,7 @@ public:
private: private:
void PruneTerminatedAppletsLocked(); void PruneTerminatedAppletsLocked();
bool RestartAppletProcessLocked(Applet* applet);
bool LockHomeMenuIntoForegroundLocked(); bool LockHomeMenuIntoForegroundLocked();
void TerminateChildAppletsLocked(Applet* applet); void TerminateChildAppletsLocked(Applet* applet);
void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false); void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false);
+2 -2
View File
@@ -82,7 +82,7 @@ void APM::OpenSession(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISession>(system, controller); rb.PushIpcInterface<ISession>(ctx, system, controller);
} }
void APM::GetPerformanceMode(HLERequestContext& ctx) { void APM::GetPerformanceMode(HLERequestContext& ctx) {
@@ -125,7 +125,7 @@ void APM_Sys::GetPerformanceEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISession>(system, controller); rb.PushIpcInterface<ISession>(ctx, system, controller);
} }
void APM_Sys::SetCpuBoostMode(HLERequestContext& ctx) { void APM_Sys::SetCpuBoostMode(HLERequestContext& ctx) {
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -45,7 +48,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IRequest>(system); rb.PushIpcInterface<IRequest>(ctx, system);
} }
}; };
+7 -7
View File
@@ -184,7 +184,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(readable_event.Signal(system.Kernel())); rb.Push(readable_event.Signal(system.Kernel()));
rb.PushCopyObjects(readable_event); rb.PushCopyObjects(ctx, readable_event);
} }
void Cancel(HLERequestContext& ctx) { void Cancel(HLERequestContext& ctx) {
@@ -400,7 +400,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(notification_event->GetReadableEvent()); rb.PushCopyObjects(ctx, notification_event->GetReadableEvent());
} }
void Clear(HLERequestContext& ctx) { void Clear(HLERequestContext& ctx) {
@@ -476,7 +476,7 @@ private:
void Module::Interface::CreateFriendService(HLERequestContext& ctx) { void Module::Interface::CreateFriendService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IFriendService>(system); rb.PushIpcInterface<IFriendService>(ctx, system);
LOG_DEBUG(Service_Friend, "called"); LOG_DEBUG(Service_Friend, "called");
} }
@@ -488,12 +488,12 @@ void Module::Interface::CreateNotificationService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<INotificationService>(system, uuid); rb.PushIpcInterface<INotificationService>(ctx, system, uuid);
} }
Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, const char* name)
const char* name) : ServiceFramework{system_, name}, module{std::move(module_)}
: ServiceFramework{system_, name}, module{std::move(module_)} {} {}
Module::Interface::~Interface() = default; Module::Interface::~Interface() = default;
+1 -1
View File
@@ -279,7 +279,7 @@ void ARP_W::AcquireRegistrar(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface(registrar); rb.PushIpcInterface(ctx, registrar);
} }
void ARP_W::UnregisterApplicationInstance(HLERequestContext& ctx) { void ARP_W::UnregisterApplicationInstance(HLERequestContext& ctx) {
+1 -1
View File
@@ -28,7 +28,7 @@ void BGTC_T::OpenTaskService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ITaskService>(system); rb.PushIpcInterface<ITaskService>(ctx, system);
} }
ITaskService::ITaskService(Core::System& system_) : ServiceFramework{system_, "ITaskService"} { ITaskService::ITaskService(Core::System& system_) : ServiceFramework{system_, "ITaskService"} {
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -56,7 +59,7 @@ ECTX_AW::~ECTX_AW() = default;
void ECTX_AW::CreateContextRegistrar(HLERequestContext& ctx) { void ECTX_AW::CreateContextRegistrar(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IContextRegistrar>(std::make_shared<IContextRegistrar>(system)); rb.PushIpcInterface<IContextRegistrar>(ctx, system);
} }
} // namespace Service::Glue } // namespace Service::Glue
@@ -726,7 +726,7 @@ void IHidSystemServer::AcquireConnectionTriggerTimeoutEvent(HLERequestContext& c
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent()); rb.PushCopyObjects(ctx, acquire_device_registered_event->GetReadableEvent());
} }
void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx) { void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx) {
@@ -734,7 +734,7 @@ void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLEReque
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent()); rb.PushCopyObjects(ctx, acquire_device_registered_event->GetReadableEvent());
} }
void IHidSystemServer::GetRegisteredDevices(HLERequestContext& ctx) { void IHidSystemServer::GetRegisteredDevices(HLERequestContext& ctx) {
@@ -759,7 +759,7 @@ void IHidSystemServer::AcquireUniquePadConnectionEventHandle(HLERequestContext&
LOG_WARNING(Service_HID, "(STUBBED) called"); LOG_WARNING(Service_HID, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.PushCopyObjects(unique_pad_connection_event->GetReadableEvent()); rb.PushCopyObjects(ctx, unique_pad_connection_event->GetReadableEvent());
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
@@ -776,7 +776,7 @@ void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestConte
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); rb.PushCopyObjects(ctx, joy_detach_event->GetReadableEvent());
} }
void IHidSystemServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { void IHidSystemServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
+2 -2
View File
@@ -31,7 +31,7 @@ class Memory;
} }
namespace IPC { namespace IPC {
class ResponseBuilder; struct ResponseBuilder;
} }
namespace Service { namespace Service {
@@ -392,7 +392,7 @@ public:
} }
private: private:
friend class IPC::ResponseBuilder; friend struct IPC::ResponseBuilder;
void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
+91 -153
View File
@@ -24,45 +24,7 @@ namespace IPC {
constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301}; constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301};
class RequestHelperBase { struct ResponseBuilder {
protected:
Service::HLERequestContext* context = nullptr;
u32* cmdbuf;
u32 index = 0;
public:
explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {}
explicit RequestHelperBase(Service::HLERequestContext& ctx)
: context(&ctx), cmdbuf(ctx.CommandBuffer()) {}
void Skip(u32 size_in_words, bool set_to_null) {
if (set_to_null) {
memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
}
index += size_in_words;
}
/**
* Aligns the current position forward to a 16-byte boundary, padding with zeros.
*/
void AlignWithPadding() {
if (index & 3) {
Skip(static_cast<u32>(4 - (index & 3)), true);
}
}
u32 GetCurrentOffset() const {
return index;
}
void SetCurrentOffset(u32 offset) {
index = offset;
}
};
class ResponseBuilder : public RequestHelperBase {
public:
/// Flags used for customizing the behavior of ResponseBuilder /// Flags used for customizing the behavior of ResponseBuilder
enum class Flags : u32 { enum class Flags : u32 {
None = 0, None = 0,
@@ -71,14 +33,13 @@ public:
AlwaysMoveHandles = 1, AlwaysMoveHandles = 1,
}; };
explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, inline explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, Flags flags = Flags::None)
u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, : cmdbuf(ctx.CommandBuffer())
Flags flags = Flags::None) , normal_params_size(normal_params_size_)
: RequestHelperBase(ctx), normal_params_size(normal_params_size_), , num_handles_to_copy(num_handles_to_copy_)
num_handles_to_copy(num_handles_to_copy_), , num_objects_to_move(num_objects_to_move_)
num_objects_to_move(num_objects_to_move_), kernel{ctx.kernel} { {
std::memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
IPC::CommandHeader header{}; IPC::CommandHeader header{};
auto const mgr = ctx.GetManager().get(); auto const mgr = ctx.GetManager().get();
@@ -117,9 +78,7 @@ public:
handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_); handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_);
handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move); handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move);
PushRaw(handle_descriptor_header); PushRaw(handle_descriptor_header);
ctx.handles_offset = index; ctx.handles_offset = index;
Skip(num_handles_to_copy + num_handles_to_move, true); Skip(num_handles_to_copy + num_handles_to_move, true);
} }
@@ -131,7 +90,6 @@ public:
domain_header.num_objects = num_domain_objects; domain_header.num_objects = num_domain_objects;
PushRaw(domain_header); PushRaw(domain_header);
} }
IPC::DataPayloadHeader data_payload_header{}; IPC::DataPayloadHeader data_payload_header{};
data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O');
PushRaw(data_payload_header); PushRaw(data_payload_header);
@@ -141,34 +99,39 @@ public:
ctx.data_payload_offset = index; ctx.data_payload_offset = index;
ctx.write_size += index; ctx.write_size += index;
ctx.domain_offset = static_cast<u32>(index + raw_data_size / sizeof(u32)); ctx.domain_offset = u32(index + raw_data_size / sizeof(u32));
} }
template <class T> inline void Skip(u32 size_in_words, bool set_to_null) {
void PushIpcInterface(std::shared_ptr<T> iface) { if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
auto manager{context->GetManager()}; index += size_in_words;
}
/// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros.
inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); }
inline u32 GetCurrentOffset() const { return index; }
inline void SetCurrentOffset(u32 offset) { index = offset; }
template <class T> inline void PushIpcInterface(Service::HLERequestContext& ctx, std::shared_ptr<T> iface) {
auto manager = ctx.GetManager();
if (manager->IsDomain()) { if (manager->IsDomain()) {
context->AddDomainObject(std::move(iface)); ctx.AddDomainObject(std::move(iface));
} else { } else {
ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(kernel, Kernel::LimitableResource::SessionCountMax, 1)); ASSERT(Kernel::GetCurrentProcess(ctx.kernel).GetResourceLimit()->Reserve(ctx.kernel, Kernel::LimitableResource::SessionCountMax, 1));
auto* session = Kernel::KSession::Create(kernel); auto* session = Kernel::KSession::Create(ctx.kernel);
session->Initialize(kernel, nullptr, 0); session->Initialize(ctx.kernel, nullptr, 0);
Kernel::KSession::Register(kernel, session); Kernel::KSession::Register(ctx.kernel, session);
auto next_manager = std::make_shared<Service::SessionRequestManager>( auto next_manager = std::make_shared<Service::SessionRequestManager>(ctx.kernel, manager->GetServerManager());
kernel, manager->GetServerManager());
next_manager->SetSessionHandler(iface); next_manager->SetSessionHandler(iface);
manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager); manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager);
context->AddMoveObject(&session->GetClientSession()); ctx.AddMoveObject(&session->GetClientSession());
} }
} }
template <class T, class... Args> template <class T, class... Args> inline void PushIpcInterface(Service::HLERequestContext& ctx, Args&&... args) {
void PushIpcInterface(Args&&... args) { PushIpcInterface<T>(ctx, std::make_shared<T>(std::forward<Args>(args)...));
PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...));
} }
void PushImpl(s8 value); void PushImpl(s8 value);
@@ -184,59 +147,38 @@ public:
void PushImpl(bool value); void PushImpl(bool value);
void PushImpl(Result value); void PushImpl(Result value);
template <typename T> template <typename T> inline void Push(T value) {
void Push(T value) {
return PushImpl(value); return PushImpl(value);
} }
template <typename First, typename... Other> template <typename First, typename... Other>
void Push(const First& first_value, const Other&... other_values); void Push(const First& first_value, const Other&... other_values);
/** /// @brief Helper function for pushing strongly-typed enumeration values.
* Helper function for pushing strongly-typed enumeration values. /// @tparam Enum The enumeration type to be pushed
* /// @param value The value to push.
* @tparam Enum The enumeration type to be pushed /// @note The underlying size of the enumeration type is the size of the data that gets pushed.
* /// e.g. "enum class SomeEnum : u16" will push a u16-sized amount of data.
* @param value The value to push. template <typename Enum> inline void PushEnum(Enum value) {
*
* @note The underlying size of the enumeration type is the size of the
* data that gets pushed. e.g. "enum class SomeEnum : u16" will
* push a u16-sized amount of data.
*/
template <typename Enum>
void PushEnum(Enum value) {
static_assert(std::is_enum_v<Enum>, "T must be an enum type within a PushEnum call."); static_assert(std::is_enum_v<Enum>, "T must be an enum type within a PushEnum call.");
static_assert(!std::is_convertible_v<Enum, int>, static_assert(!std::is_convertible_v<Enum, int>, "enum type in PushEnum must be a strongly typed enum.");
"enum type in PushEnum must be a strongly typed enum.");
Push(static_cast<std::underlying_type_t<Enum>>(value)); Push(static_cast<std::underlying_type_t<Enum>>(value));
} }
/** /// @brief Copies the content of the given trivially copyable class to the buffer as a normal param
* @brief Copies the content of the given trivially copyable class to the buffer as a normal /// @note: The input class must be correctly packed/padded to fit hardware layout.
* param template <typename T> void PushRaw(const T& value);
* @note: The input class must be correctly packed/padded to fit hardware layout. template <typename... O> void PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers);
*/ template <typename... O> void PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers);
template <typename T> template <typename... O> void PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers);
void PushRaw(const T& value); template <typename... O> void PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers);
template <typename... O> u32* cmdbuf;
void PushMoveObjects(O*... pointers); u32 index = 0;
template <typename... O>
void PushMoveObjects(O&... pointers);
template <typename... O>
void PushCopyObjects(O*... pointers);
template <typename... O>
void PushCopyObjects(O&... pointers);
private:
u32 normal_params_size{}; u32 normal_params_size{};
u32 num_handles_to_copy{}; u32 num_handles_to_copy{};
u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent
u32 data_payload_index{}; u32 data_payload_index{};
Kernel::KernelCore& kernel;
}; };
/// Push /// /// Push ///
@@ -251,8 +193,7 @@ inline void ResponseBuilder::PushImpl(u32 value) {
template <typename T> template <typename T>
void ResponseBuilder::PushRaw(const T& value) { void ResponseBuilder::PushRaw(const T& value) {
static_assert(std::is_trivially_copyable_v<T>, static_assert(std::is_trivially_copyable_v<T>, "It's undefined behavior to use memcpy with non-trivially copyable objects");
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(cmdbuf + index, &value, sizeof(T)); std::memcpy(cmdbuf + index, &value, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length index += (sizeof(T) + 3) / 4; // round up to word length
} }
@@ -272,8 +213,8 @@ inline void ResponseBuilder::PushImpl(s16 value) {
} }
inline void ResponseBuilder::PushImpl(s64 value) { inline void ResponseBuilder::PushImpl(s64 value) {
PushImpl(static_cast<u32>(value)); PushImpl(u32(value));
PushImpl(static_cast<u32>(value >> 32)); PushImpl(u32(value >> 32));
} }
inline void ResponseBuilder::PushImpl(u8 value) { inline void ResponseBuilder::PushImpl(u8 value) {
@@ -285,8 +226,8 @@ inline void ResponseBuilder::PushImpl(u16 value) {
} }
inline void ResponseBuilder::PushImpl(u64 value) { inline void ResponseBuilder::PushImpl(u64 value) {
PushImpl(static_cast<u32>(value)); PushImpl(u32(value));
PushImpl(static_cast<u32>(value >> 32)); PushImpl(u32(value >> 32));
} }
inline void ResponseBuilder::PushImpl(float value) { inline void ResponseBuilder::PushImpl(float value) {
@@ -312,90 +253,88 @@ void ResponseBuilder::Push(const First& first_value, const Other&... other_value
} }
template <typename... O> template <typename... O>
inline void ResponseBuilder::PushCopyObjects(O*... pointers) { inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers) {
auto objects = {pointers...}; auto objects = {pointers...};
for (auto& object : objects) { for (auto& object : objects) {
context->AddCopyObject(object); ctx.AddCopyObject(object);
} }
} }
template <typename... O> template <typename... O>
inline void ResponseBuilder::PushCopyObjects(O&... pointers) { inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers) {
auto objects = {&pointers...}; auto objects = {&pointers...};
for (auto& object : objects) { for (auto& object : objects) {
context->AddCopyObject(object); ctx.AddCopyObject(object);
} }
} }
template <typename... O> template <typename... O>
inline void ResponseBuilder::PushMoveObjects(O*... pointers) { inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers) {
auto objects = {pointers...}; auto objects = {pointers...};
for (auto& object : objects) { for (auto& object : objects) {
context->AddMoveObject(object); ctx.AddMoveObject(object);
} }
} }
template <typename... O> template <typename... O>
inline void ResponseBuilder::PushMoveObjects(O&... pointers) { inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers) {
auto objects = {&pointers...}; auto objects = {&pointers...};
for (auto& object : objects) { for (auto& object : objects) {
context->AddMoveObject(object); ctx.AddMoveObject(object);
} }
} }
class RequestParser : public RequestHelperBase { struct RequestParser {
public: inline explicit RequestParser(u32* command_buffer) : cmdbuf(command_buffer) {}
explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {} inline explicit RequestParser(Service::HLERequestContext& ctx)
: cmdbuf(ctx.CommandBuffer())
explicit RequestParser(Service::HLERequestContext& ctx) : RequestHelperBase(ctx) { {
// TIPC does not have data payload offset // TIPC does not have data payload offset
if (!ctx.IsTipc()) { if (!ctx.IsTipc()) {
ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete");
Skip(ctx.GetDataPayloadOffset(), false); Skip(ctx.GetDataPayloadOffset(), false);
} }
// Skip the u64 command id, it's already stored in the context // Skip the u64 command id, it's already stored in the context
static constexpr u32 CommandIdSize = 2; static constexpr u32 CommandIdSize = 2;
Skip(CommandIdSize, false); Skip(CommandIdSize, false);
} }
template <typename T> inline void Skip(u32 size_in_words, bool set_to_null) {
T Pop(); if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
index += size_in_words;
}
/// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros.
inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); }
inline u32 GetCurrentOffset() const { return index; }
inline void SetCurrentOffset(u32 offset) { index = offset; }
template <typename T> template <typename T> T Pop();
void Pop(T& value); template <typename T> void Pop(T& value);
template <typename First, typename... Other> void Pop(First& first_value, Other&... other_values);
template <typename First, typename... Other>
void Pop(First& first_value, Other&... other_values);
template <typename T> template <typename T>
T PopEnum() { T PopEnum() {
static_assert(std::is_enum_v<T>, "T must be an enum type within a PopEnum call."); static_assert(std::is_enum_v<T>, "T must be an enum type within a PopEnum call.");
static_assert(!std::is_convertible_v<T, int>, static_assert(!std::is_convertible_v<T, int>, "enum type in PopEnum must be a strongly typed enum.");
"enum type in PopEnum must be a strongly typed enum."); return T(Pop<std::underlying_type_t<T>>());
return static_cast<T>(Pop<std::underlying_type_t<T>>());
} }
/** /// @brief Reads the next normal parameters as a struct, by copying it
* @brief Reads the next normal parameters as a struct, by copying it /// @note: The output class must be correctly packed/padded to fit hardware layout.
* @note: The output class must be correctly packed/padded to fit hardware layout. template <typename T> void PopRaw(T& value);
*/
template <typename T>
void PopRaw(T& value);
/** /// @brief Reads the next normal parameters as a struct, by copying it into a new value
* @brief Reads the next normal parameters as a struct, by copying it into a new value /// @note: The output class must be correctly packed/padded to fit hardware layout.
* @note: The output class must be correctly packed/padded to fit hardware layout. template <typename T> T PopRaw();
*/
template <typename T>
T PopRaw();
template <class T> template <class T> [[nodiscard]] std::weak_ptr<T> PopIpcInterface(Service::HLERequestContext& ctx) {
std::weak_ptr<T> PopIpcInterface() { ASSERT(ctx.GetManager()->IsDomain());
ASSERT(context->GetManager()->IsDomain()); ASSERT(ctx.GetDomainMessageHeader().input_object_count > 0);
ASSERT(context->GetDomainMessageHeader().input_object_count > 0); return ctx.GetDomainHandler<T>(Pop<u32>() - 1);
return context->GetDomainHandler<T>(Pop<u32>() - 1);
} }
u32* cmdbuf;
u32 index = 0;
}; };
/// Pop /// /// Pop ///
@@ -407,7 +346,7 @@ inline u32 RequestParser::Pop() {
template <> template <>
inline s32 RequestParser::Pop() { inline s32 RequestParser::Pop() {
return static_cast<s32>(Pop<u32>()); return s32(Pop<u32>());
} }
// Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. // Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects.
@@ -417,8 +356,7 @@ inline s32 RequestParser::Pop() {
#endif #endif
template <typename T> template <typename T>
void RequestParser::PopRaw(T& value) { void RequestParser::PopRaw(T& value) {
static_assert(std::is_trivially_copyable_v<T>, static_assert(std::is_trivially_copyable_v<T>, "It's undefined behavior to use memcpy with non-trivially copyable objects");
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, cmdbuf + index, sizeof(T)); std::memcpy(&value, cmdbuf + index, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length index += (sizeof(T) + 3) / 4; // round up to word length
} }
+1 -1
View File
@@ -353,7 +353,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ILogger>(system); rb.PushIpcInterface<ILogger>(ctx, system);
} }
}; };
+4 -4
View File
@@ -151,7 +151,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IAm>(system); rb.PushIpcInterface<IAm>(ctx, system);
} }
}; };
@@ -173,7 +173,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<MFIUser>(system); rb.PushIpcInterface<MFIUser>(ctx, system);
} }
}; };
@@ -195,7 +195,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IUser>(system); rb.PushIpcInterface<IUser>(ctx, system);
} }
}; };
@@ -217,7 +217,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISystem>(system); rb.PushIpcInterface<ISystem>(ctx, system);
} }
}; };
+3 -3
View File
@@ -143,7 +143,7 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(GetManager()->AttachAvailabilityChangeEvent()); rb.PushCopyObjects(ctx, GetManager()->AttachAvailabilityChangeEvent());
} }
void NfcInterface::StartDetection(HLERequestContext& ctx) { void NfcInterface::StartDetection(HLERequestContext& ctx) {
@@ -203,7 +203,7 @@ void NfcInterface::AttachActivateEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(result); rb.Push(result);
rb.PushCopyObjects(out_event); rb.PushCopyObjects(ctx, out_event);
} }
void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) { void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) {
@@ -217,7 +217,7 @@ void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(result); rb.Push(result);
rb.PushCopyObjects(out_event); rb.PushCopyObjects(ctx, out_event);
} }
void NfcInterface::SetNfcEnabled(HLERequestContext& ctx) { void NfcInterface::SetNfcEnabled(HLERequestContext& ctx) {
+3 -3
View File
@@ -157,7 +157,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IUser>(system); rb.PushIpcInterface<IUser>(ctx, system);
} }
}; };
@@ -179,7 +179,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISystem>(system); rb.PushIpcInterface<ISystem>(ctx, system);
} }
}; };
@@ -201,7 +201,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebug>(system); rb.PushIpcInterface<IDebug>(ctx, system);
} }
}; };
+8 -8
View File
@@ -307,7 +307,7 @@ private:
void GetSystemEventReadableHandle(HLERequestContext& ctx) { void GetSystemEventReadableHandle(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 2}; IPC::ResponseBuilder rb{ctx, 2, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(evt_scan_complete->GetReadableEvent(), rb.PushCopyObjects(ctx, evt_scan_complete->GetReadableEvent(),
evt_processing->GetReadableEvent()); evt_processing->GetReadableEvent());
} }
@@ -452,7 +452,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 2}; IPC::ResponseBuilder rb{ctx, 2, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(event1->GetReadableEvent(), event2->GetReadableEvent()); rb.PushCopyObjects(ctx, event1->GetReadableEvent(), event2->GetReadableEvent());
} }
void Cancel(HLERequestContext& ctx) { void Cancel(HLERequestContext& ctx) {
@@ -528,7 +528,7 @@ void IGeneralService::CreateScanRequest(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IScanRequest>(system); rb.PushIpcInterface<IScanRequest>(ctx, system);
} }
void IGeneralService::CreateRequest(HLERequestContext& ctx) { void IGeneralService::CreateRequest(HLERequestContext& ctx) {
@@ -537,7 +537,7 @@ void IGeneralService::CreateRequest(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IRequest>(system); rb.PushIpcInterface<IRequest>(ctx, system);
} }
void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) {
@@ -716,7 +716,7 @@ void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<INetworkProfile>(system); rb.PushIpcInterface<INetworkProfile>(ctx, system);
} }
void IGeneralService::SetNetworkProfile(HLERequestContext& ctx) { void IGeneralService::SetNetworkProfile(HLERequestContext& ctx) {
@@ -869,7 +869,7 @@ void IGeneralService::CreateTemporaryNetworkProfile(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 6, 0, 1}; IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<INetworkProfile>(system); rb.PushIpcInterface<INetworkProfile>(ctx, system);
rb.PushRaw<u128>(uuid); rb.PushRaw<u128>(uuid);
} }
@@ -1124,7 +1124,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IGeneralService>(system); rb.PushIpcInterface<IGeneralService>(ctx, system);
} }
void CreateGeneralService(HLERequestContext& ctx) { void CreateGeneralService(HLERequestContext& ctx) {
@@ -1132,7 +1132,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IGeneralService>(system); rb.PushIpcInterface<IGeneralService>(ctx, system);
} }
}; };
+6 -6
View File
@@ -53,7 +53,7 @@ private:
LOG_WARNING(Service_NIM, "(STUBBED) called"); LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IShopServiceAsync>(system); rb.PushIpcInterface<IShopServiceAsync>(ctx, system);
} }
}; };
@@ -75,7 +75,7 @@ private:
LOG_WARNING(Service_NIM, "(STUBBED) called"); LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IShopServiceAccessor>(system); rb.PushIpcInterface<IShopServiceAccessor>(ctx, system);
} }
}; };
@@ -336,7 +336,7 @@ private:
LOG_DEBUG(Service_NIM, "(STUBBED) called"); LOG_DEBUG(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IShopServiceAccessServer>(system); rb.PushIpcInterface<IShopServiceAccessServer>(ctx, system);
} }
void IsLargeResourceAvailable(HLERequestContext& ctx) { void IsLargeResourceAvailable(HLERequestContext& ctx) {
@@ -356,7 +356,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IShopServiceAccessServer>(system); rb.PushIpcInterface<IShopServiceAccessServer>(ctx, system);
} }
}; };
@@ -439,7 +439,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(finished_event->GetReadableEvent()); rb.PushCopyObjects(ctx, finished_event->GetReadableEvent());
} }
void GetResult(HLERequestContext& ctx) { void GetResult(HLERequestContext& ctx) {
@@ -500,7 +500,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(system); rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(ctx, system);
} }
// TODO(ogniK): Do we need these? // TODO(ogniK): Do we need these?
@@ -359,8 +359,8 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestCo
IPC::ResponseBuilder rb{ctx, 2, 1, 1}; IPC::ResponseBuilder rb{ctx, 2, 1, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(async_value->ReadableEvent()); rb.PushCopyObjects(ctx, async_value->ReadableEvent());
rb.PushIpcInterface(std::move(async_value)); rb.PushIpcInterface(ctx, std::move(async_value));
} }
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
@@ -199,7 +199,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3, 1}; IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
auto& readable_event = event->GetReadableEvent(); auto& readable_event = event->GetReadableEvent();
rb.PushCopyObjects(readable_event); rb.PushCopyObjects(ctx, readable_event);
rb.PushEnum(NvResult::Success); rb.PushEnum(NvResult::Success);
} else { } else {
LOG_ERROR(Service_NVDRV, "Invalid event request!"); LOG_ERROR(Service_NVDRV, "Invalid event request!");
+11
View File
@@ -6,6 +6,8 @@
#pragma once #pragma once
#include <utility>
#include "common/common_types.h" #include "common/common_types.h"
namespace Core { namespace Core {
@@ -28,6 +30,15 @@ public:
inline explicit Process(Core::System& system) noexcept : m_system(system) {} inline explicit Process(Core::System& system) noexcept : m_system(system) {}
inline ~Process() { this->Finalize(); } inline ~Process() { this->Finalize(); }
Process(const Process&) = delete;
Process& operator=(const Process&) = delete;
Process& operator=(Process&&) = delete;
inline Process(Process&& other) noexcept
: m_system(other.m_system), m_process(std::exchange(other.m_process, nullptr)),
m_main_thread_stack_size(std::exchange(other.m_main_thread_stack_size, 0)),
m_main_thread_priority(std::exchange(other.m_main_thread_priority, 0)),
m_process_started(std::exchange(other.m_process_started, false)) {}
bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result); bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result);
void Finalize(); void Finalize();
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -124,7 +127,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IClkrstSession>(system, device_code); rb.PushIpcInterface<IClkrstSession>(ctx, system, device_code);
} }
}; };
+1 -1
View File
@@ -161,7 +161,7 @@ private:
IPC::ResponseBuilder rb{ctx, 10, 1}; IPC::ResponseBuilder rb{ctx, 10, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(*process); rb.PushCopyObjects(ctx, *process);
rb.PushRaw(program_location); rb.PushRaw(program_location);
rb.PushRaw(override_status); rb.PushRaw(override_status);
} }
+3 -3
View File
@@ -147,7 +147,7 @@ void IAlarmService::CreateWakeupAlarm(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISteadyClockAlarm>(system, m_alarms, AlarmType::WakeupAlarm); rb.PushIpcInterface<ISteadyClockAlarm>(ctx, system, m_alarms, AlarmType::WakeupAlarm);
} }
void IAlarmService::CreateBackgroundTaskAlarm(HLERequestContext& ctx) { void IAlarmService::CreateBackgroundTaskAlarm(HLERequestContext& ctx) {
@@ -155,7 +155,7 @@ void IAlarmService::CreateBackgroundTaskAlarm(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISteadyClockAlarm>(system, m_alarms, AlarmType::BackgroundTaskAlarm); rb.PushIpcInterface<ISteadyClockAlarm>(ctx, system, m_alarms, AlarmType::BackgroundTaskAlarm);
} }
ISteadyClockAlarm::ISteadyClockAlarm(Core::System& system_, Alarms& alarms, AlarmType type) ISteadyClockAlarm::ISteadyClockAlarm(Core::System& system_, Alarms& alarms, AlarmType type)
@@ -179,7 +179,7 @@ void ISteadyClockAlarm::GetAlarmEvent(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(m_alarm.GetEventHandle()); rb.PushCopyObjects(ctx, m_alarm.GetEventHandle());
} }
void ISteadyClockAlarm::Enable(HLERequestContext& ctx) { void ISteadyClockAlarm::Enable(HLERequestContext& ctx) {
+2 -2
View File
@@ -67,7 +67,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushCopyObjects(state_change_event->GetReadableEvent()); rb.PushCopyObjects(ctx, state_change_event->GetReadableEvent());
} }
void UnbindStateChangeEvent(HLERequestContext& ctx) { void UnbindStateChangeEvent(HLERequestContext& ctx) {
@@ -186,7 +186,7 @@ void PSM::OpenSession(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IPsmSession>(system); rb.PushIpcInterface<IPsmSession>(ctx, system);
} }
void PSM::GetBatteryVoltageState(HLERequestContext& ctx) { void PSM::GetBatteryVoltageState(HLERequestContext& ctx) {
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@@ -82,7 +85,7 @@ void TS::OpenSession(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISession>(system); rb.PushIpcInterface<ISession>(ctx, system);
} }
} // namespace Service::PTM } // namespace Service::PTM
+4 -5
View File
@@ -140,7 +140,7 @@ void SM::GetServiceCmif(HLERequestContext& ctx) {
if (result == ResultSuccess) { if (result == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(result); rb.Push(result);
rb.PushMoveObjects(client_session); rb.PushMoveObjects(ctx, client_session);
} else { } else {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result); rb.Push(result);
@@ -157,7 +157,7 @@ void SM::GetServiceTipc(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(result); rb.Push(result);
rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr); rb.PushMoveObjects(ctx, result == ResultSuccess ? client_session : nullptr);
} }
static std::string PopServiceName(IPC::RequestParser& rp) { static std::string PopServiceName(IPC::RequestParser& rp) {
@@ -230,8 +230,7 @@ void SM::RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_s
max_session_count, is_light); max_session_count, is_light);
Kernel::KServerPort* server_port{}; Kernel::KServerPort* server_port{};
if (const auto result = service_manager.RegisterService(std::addressof(server_port), name, if (const auto result = service_manager.RegisterService(std::addressof(server_port), name, max_session_count, nullptr);
max_session_count, nullptr);
result.IsError()) { result.IsError()) {
LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}", result.raw); LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}", result.raw);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
@@ -241,7 +240,7 @@ void SM::RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_s
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushMoveObjects(server_port); rb.PushMoveObjects(ctx, server_port);
} }
void SM::UnregisterService(HLERequestContext& ctx) { void SM::UnregisterService(HLERequestContext& ctx) {
+1 -1
View File
@@ -60,7 +60,7 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) {
// We succeeded. // We succeeded.
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushMoveObjects(session->GetClientSession()); rb.PushMoveObjects(ctx, session->GetClientSession());
} }
void Controller::CloneCurrentObjectEx(HLERequestContext& ctx) { void Controller::CloneCurrentObjectEx(HLERequestContext& ctx) {
+17 -11
View File
@@ -24,9 +24,6 @@
#include "network/network.h" #include "network/network.h"
#include <common/settings.h> #include <common/settings.h>
using Common::Expected;
using Common::Unexpected;
namespace Service::Sockets { namespace Service::Sockets {
namespace { namespace {
@@ -464,13 +461,22 @@ void BSD::DuplicateSocket(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
auto input = rp.PopRaw<InputParameters>(); auto input = rp.PopRaw<InputParameters>();
Expected<s32, Errno> res = DuplicateSocketImpl(input.fd);
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushRaw(OutputParameters{
.ret = res.value_or(0), auto const res_v = DuplicateSocketImpl(input.fd);
.bsd_errno = res ? Errno::SUCCESS : res.error(), if (auto* res = std::get_if<s32>(&res_v)) {
}); rb.PushRaw(OutputParameters{
.ret = *res,
.bsd_errno = Errno::SUCCESS,
});
} else {
auto* err = std::get_if<Errno>(&res_v);
rb.PushRaw(OutputParameters{
.ret = 0,
.bsd_errno = *err,
});
}
} }
void BSD::EventFd(HLERequestContext& ctx) { void BSD::EventFd(HLERequestContext& ctx) {
@@ -977,15 +983,15 @@ Errno BSD::CloseImpl(s32 fd) {
return bsd_errno; return bsd_errno;
} }
Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) { std::variant<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Unexpected(Errno::BADF); return Errno::BADF;
} }
const s32 new_fd = FindFreeFileDescriptorHandle(); const s32 new_fd = FindFreeFileDescriptorHandle();
if (new_fd < 0) { if (new_fd < 0) {
LOG_ERROR(Service, "No more file descriptors available"); LOG_ERROR(Service, "No more file descriptors available");
return Unexpected(Errno::MFILE); return Errno::MFILE;
} }
file_descriptors[new_fd] = FileDescriptor{ file_descriptors[new_fd] = FileDescriptor{
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -8,9 +8,9 @@
#include <memory> #include <memory>
#include <span> #include <span>
#include <vector> #include <vector>
#include <variant>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/expected.h"
#include "common/socket_types.h" #include "common/socket_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/hle/service/sockets/sockets.h" #include "core/hle/service/sockets/sockets.h"
@@ -35,7 +35,7 @@ public:
// These methods are called from SSL; the first two are also called from // These methods are called from SSL; the first two are also called from
// this class for the corresponding IPC methods. // this class for the corresponding IPC methods.
// On the real device, the SSL service makes IPC calls to this service. // On the real device, the SSL service makes IPC calls to this service.
Common::Expected<s32, Errno> DuplicateSocketImpl(s32 fd); std::variant<s32, Errno> DuplicateSocketImpl(s32 fd);
Errno CloseImpl(s32 fd); Errno CloseImpl(s32 fd);
std::optional<std::shared_ptr<Network::SocketBase>> GetSocket(s32 fd); std::optional<std::shared_ptr<Network::SocketBase>> GetSocket(s32 fd);
+19 -20
View File
@@ -88,11 +88,12 @@ static const constexpr std::array blockedDomains = {
"sun.hac.lp1.d4c.nintendo.net", "sun.hac.lp1.d4c.nintendo.net",
"phoenix-api.wbagora.com", //hogwarts legacy "phoenix-api.wbagora.com", //hogwarts legacy
"battle.net", "battle.net",
"microsoft.com", //minecraft dungeons + other games "microsoft.com", // Minecraft dungeons + other games
"mojang.com", "mojang.com",
"xboxlive.com", "xboxlive.com",
"api.epicgames.dev", // marvel cosmic invasion +? "api.epicgames.dev", // marvel cosmic invasion +?
"minecraftservices.com" "minecraftservices.com",
"508223012e5a5ff19f30a391b2bdadc0.my.2k.com", // Civilization 5
}; };
static bool IsBlockedHost(const std::string& host) { static bool IsBlockedHost(const std::string& host) {
@@ -207,16 +208,15 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
return {0, GetAddrInfoError::AGAIN}; return {0, GetAddrInfoError::AGAIN};
} }
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); auto res_v = Network::GetAddressInfo(host, /*service*/ std::nullopt);
if (!res.has_value()) { if (auto* res = std::get_if<std::vector<Network::AddrInfo>>(&res_v)) {
return {0, Translate(res.error())}; const std::vector<u8> data = SerializeAddrInfoAsHostEnt(*res, host);
const u32 data_size = u32(data.size());
ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS};
} }
auto* err = std::get_if<Network::GetAddrInfoError>(&res_v);
const std::vector<u8> data = SerializeAddrInfoAsHostEnt(res.value(), host); return {0, Translate(*err)};
const u32 data_size = static_cast<u32>(data.size());
ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS};
} }
void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) {
@@ -332,16 +332,15 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
// Serialized hints are also passed in a buffer, but are ignored for now. // Serialized hints are also passed in a buffer, but are ignored for now.
auto res = Network::GetAddressInfo(host, service); auto res_v = Network::GetAddressInfo(host, service);
if (!res.has_value()) { if (auto* res = std::get_if<std::vector<Network::AddrInfo>>(&res_v)) {
return {0, Translate(res.error())}; const std::vector<u8> data = SerializeAddrInfo(*res, host);
const u32 data_size = u32(data.size());
ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS};
} }
auto* err = std::get_if<Network::GetAddrInfoError>(&res_v);
const std::vector<u8> data = SerializeAddrInfo(res.value(), host); return {0, Translate(*err)};
const u32 data_size = static_cast<u32>(data.size());
ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS};
} }
void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
+22 -27
View File
@@ -160,29 +160,26 @@ private:
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u"); auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
auto res = bsd->DuplicateSocketImpl(fd); auto const res_v = bsd->DuplicateSocketImpl(fd);
if (!res.has_value()) { if (auto *res = std::get_if<s32>(&res_v)) {
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd); const s32 duplicated_fd = *res;
return ResultInvalidSocket; if (do_not_close_socket) {
*out_fd = duplicated_fd;
} else {
*out_fd = -1;
fd_to_close = duplicated_fd;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
if (!sock.has_value()) {
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
return ResultInvalidSocket;
}
socket = std::move(*sock);
backend->SetSocket(socket);
return ResultSuccess;
} }
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
const s32 duplicated_fd = *res; return ResultInvalidSocket;
if (do_not_close_socket) {
*out_fd = duplicated_fd;
} else {
*out_fd = -1;
fd_to_close = duplicated_fd;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
if (!sock.has_value()) {
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
return ResultInvalidSocket;
}
socket = std::move(*sock);
backend->SetSocket(socket);
return ResultSuccess;
} }
Result SetHostNameImpl(const std::string& hostname) { Result SetHostNameImpl(const std::string& hostname) {
@@ -546,8 +543,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(res); rb.Push(res);
if (res == ResultSuccess) { if (res == ResultSuccess) {
rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data, rb.PushIpcInterface<ISslConnection>(ctx, system, ssl_version, shared_data, std::move(backend));
std::move(backend));
} }
} }
@@ -627,12 +623,11 @@ private:
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto parameters = rp.PopRaw<Parameters>(); const auto parameters = rp.PopRaw<Parameters>();
LOG_WARNING(Service_SSL, "(STUBBED) called, api_version={}, pid_placeholder={}", LOG_WARNING(Service_SSL, "(STUBBED) called, api_version={}, pid_placeholder={}", parameters.ssl_version.api_version, parameters.pid_placeholder);
parameters.ssl_version.api_version, parameters.pid_placeholder);
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<ISslContext>(system, parameters.ssl_version); rb.PushIpcInterface<ISslContext>(ctx, system, parameters.ssl_version);
} }
void SetInterfaceVersion(HLERequestContext& ctx) { void SetInterfaceVersion(HLERequestContext& ctx) {
+2 -2
View File
@@ -155,7 +155,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IPdSession>(system); rb.PushIpcInterface<IPdSession>(ctx, system);
} }
}; };
@@ -199,7 +199,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IPdCradleSession>(system); rb.PushIpcInterface<IPdCradleSession>(ctx, system);
} }
}; };
+3 -5
View File
@@ -28,7 +28,6 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/expected.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/internal_network/network.h" #include "core/internal_network/network.h"
@@ -733,15 +732,14 @@ u32 IPv4AddressToInteger(IPv4Address ip_addr) {
static_cast<u32>(ip_addr[2]) << 8 | static_cast<u32>(ip_addr[3]); static_cast<u32>(ip_addr[2]) << 8 | static_cast<u32>(ip_addr[3]);
} }
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo( std::variant<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
const std::string& host, const std::optional<std::string>& service) { const std::string& host, const std::optional<std::string>& service) {
addrinfo hints{}; addrinfo hints{};
hints.ai_family = AF_INET; // Switch only supports IPv4. hints.ai_family = AF_INET; // Switch only supports IPv4.
addrinfo* addrinfo; addrinfo* addrinfo;
s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr, s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr, &hints, &addrinfo);
&hints, &addrinfo);
if (gai_err != 0) { if (gai_err != 0) {
return Common::Unexpected(TranslateGetAddrInfoErrorFromNative(gai_err)); return TranslateGetAddrInfoErrorFromNative(gai_err);
} }
std::vector<AddrInfo> ret; std::vector<AddrInfo> ret;
for (auto* current = addrinfo; current; current = current->ai_next) { for (auto* current = addrinfo; current; current = current->ai_next) {
+2 -2
View File
@@ -9,6 +9,7 @@
#include <array> #include <array>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <variant>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
@@ -124,7 +125,6 @@ std::string IPv4AddressToString(IPv4Address ip_addr);
u32 IPv4AddressToInteger(IPv4Address ip_addr); u32 IPv4AddressToInteger(IPv4Address ip_addr);
// named to avoid name collision with Windows macro // named to avoid name collision with Windows macro
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo( std::variant<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(const std::string& host, const std::optional<std::string>& service);
const std::string& host, const std::optional<std::string>& service);
} // namespace Network } // namespace Network
+2 -2
View File
@@ -50,8 +50,8 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service
auto& controller = controller_data[aruid_index][i]; auto& controller = controller_data[aruid_index][i];
controller.device = hid_core.GetEmulatedControllerByIndex(i); controller.device = hid_core.GetEmulatedControllerByIndex(i);
Core::HID::ControllerUpdateCallback engine_callback{ Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this, i](Core::HID::ControllerTriggerType type) { .on_change = [this, i, kernel = &hid_core.kernel](Core::HID::ControllerTriggerType type) {
ControllerUpdate(hid_core.kernel, type, i); ControllerUpdate(*kernel, type, i);
}, },
.is_npad_service = true, .is_npad_service = true,
}; };
@@ -229,6 +229,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but " tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but "
"may degrade performance.")); "may degrade performance."));
INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"),
tr("Preserves GPU-modified buffer data by reading it back before uploads.\nSome games require this to render certain effects properly.\nMay cause issues if the hardware cannot handle the additional workload."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
@@ -236,8 +236,11 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) { Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
HostTranslateInfo normalized_host_info{host_info};
normalized_host_info.ApplyDescriptorLimitPolicy();
IR::Program program; IR::Program program;
program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, host_info); program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, normalized_host_info);
program.blocks = GenerateBlocks(program.syntax_list); program.blocks = GenerateBlocks(program.syntax_list);
program.post_order_blocks = PostOrder(program.syntax_list.front()); program.post_order_blocks = PostOrder(program.syntax_list.front());
program.stage = env.ShaderStage(); program.stage = env.ShaderStage();
@@ -260,9 +263,9 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
program.info.passthrough.mask[i] = ((mask[i / 32] >> (i % 32)) & 1) == 0; program.info.passthrough.mask[i] = ((mask[i / 32] >> (i % 32)) & 1) == 0;
} }
if (!host_info.support_geometry_shader_passthrough) { if (!normalized_host_info.support_geometry_shader_passthrough) {
program.output_vertices = GetOutputTopologyVertices(program.output_topology); program.output_vertices = GetOutputTopologyVertices(program.output_topology);
LowerGeometryPassthrough(program, host_info); LowerGeometryPassthrough(program, normalized_host_info);
} }
} }
break; break;
@@ -277,16 +280,16 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
RemoveUnreachableBlocks(program); RemoveUnreachableBlocks(program);
// Replace instructions before the SSA rewrite // Replace instructions before the SSA rewrite
if (!host_info.support_float64) { if (!normalized_host_info.support_float64) {
Optimization::LowerFp64ToFp32(program); Optimization::LowerFp64ToFp32(program);
} }
if (!host_info.support_float16) { if (!normalized_host_info.support_float16) {
Optimization::LowerFp16ToFp32(program); Optimization::LowerFp16ToFp32(program);
} }
if (!host_info.support_int64) { if (!normalized_host_info.support_int64) {
Optimization::LowerInt64ToInt32(program); Optimization::LowerInt64ToInt32(program);
} }
if (!host_info.support_conditional_barrier) { if (!normalized_host_info.support_conditional_barrier) {
Optimization::ConditionalBarrierPass(program); Optimization::ConditionalBarrierPass(program);
} }
Optimization::SsaRewritePass(program); Optimization::SsaRewritePass(program);
@@ -295,8 +298,8 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::PositionPass(env, program); Optimization::PositionPass(env, program);
Optimization::GlobalMemoryToStorageBufferPass(program, host_info); Optimization::GlobalMemoryToStorageBufferPass(program, normalized_host_info);
Optimization::TexturePass(env, program, host_info); Optimization::TexturePass(env, program, normalized_host_info);
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) { if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
Optimization::RescalingPass(program); Optimization::RescalingPass(program);
@@ -306,7 +309,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::VerificationPass(program); Optimization::VerificationPass(program);
} }
Optimization::CollectShaderInfoPass(env, program); Optimization::CollectShaderInfoPass(env, program);
Optimization::LayerPass(program, host_info); Optimization::LayerPass(program, normalized_host_info);
Optimization::VendorWorkaroundPass(program); Optimization::VendorWorkaroundPass(program);
CollectInterpolationInfo(env, program); CollectInterpolationInfo(env, program);
+38 -4
View File
@@ -6,6 +6,8 @@
#pragma once #pragma once
#include "common/common_types.h"
namespace Shader { namespace Shader {
// Try to keep entries here to a minimum // Try to keep entries here to a minimum
@@ -13,20 +15,52 @@ namespace Shader {
/// Misc information about the host /// Misc information about the host
struct HostTranslateInfo { struct HostTranslateInfo {
static constexpr u32 DEFAULT_DESCRIPTOR_LIMIT = 1024;
u64 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
u32 max_per_stage_descriptor_sampled_images{}; ///< maximum sampled descriptors per stage
u32 max_per_stage_resources{}; ///< maximum resources per stage
u32 max_descriptor_set_samplers{};
u32 max_descriptor_set_uniform_buffers{};
u32 max_descriptor_set_uniform_buffers_dynamic{};
u32 max_descriptor_set_storage_buffers{};
u32 max_descriptor_set_storage_buffers_dynamic{};
u32 max_descriptor_set_sampled_images{};
u32 max_descriptor_set_storage_images{};
u32 max_descriptor_set_input_attachements{};
bool support_float64{}; ///< True when the device supports 64-bit floats bool support_float64{}; ///< True when the device supports 64-bit floats
bool support_float16{}; ///< True when the device supports 16-bit floats bool support_float16{}; ///< True when the device supports 16-bit floats
bool support_int64{}; ///< True when the device supports 64-bit integers bool support_int64{}; ///< True when the device supports 64-bit integers
bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
u32 max_per_stage_descriptor_sampled_images{1024}; ///< maximum sampled descriptors per stage
u32 max_per_stage_resources{4096}; ///< maximum resources per stage
u32 max_descriptor_set_sampled_images{1024}; ///< maximum sampled descriptors per set
bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
///< passthrough shaders ///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
///< control flow ///< control flow
void ApplyDescriptorLimitPolicy() noexcept {
if (min_ssbo_alignment == 0) {
min_ssbo_alignment = 1;
}
ApplyDescriptorLimitFallback(max_per_stage_descriptor_sampled_images);
ApplyDescriptorLimitFallback(max_per_stage_resources);
ApplyDescriptorLimitFallback(max_descriptor_set_samplers);
ApplyDescriptorLimitFallback(max_descriptor_set_uniform_buffers);
ApplyDescriptorLimitFallback(max_descriptor_set_uniform_buffers_dynamic);
ApplyDescriptorLimitFallback(max_descriptor_set_storage_buffers);
ApplyDescriptorLimitFallback(max_descriptor_set_storage_buffers_dynamic);
ApplyDescriptorLimitFallback(max_descriptor_set_sampled_images);
ApplyDescriptorLimitFallback(max_descriptor_set_storage_images);
ApplyDescriptorLimitFallback(max_descriptor_set_input_attachements);
}
private:
static void ApplyDescriptorLimitFallback(u32& limit) noexcept {
if (limit == 0) {
limit = DEFAULT_DESCRIPTOR_LIMIT;
}
}
}; };
} // namespace Shader } // namespace Shader
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -545,7 +548,7 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn
IR::Block* const block{storage_inst.block}; IR::Block* const block{storage_inst.block};
IR::Inst* const inst{storage_inst.inst}; IR::Inst* const inst{storage_inst.inst};
const IR::U32 offset{ const IR::U32 offset{
StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)}; StorageOffset(*block, *inst, storage_buffer, u32(host_info.min_ssbo_alignment))};
Replace(*block, *inst, index, offset); Replace(*block, *inst, index, offset);
} }
} }
+65 -71
View File
@@ -32,71 +32,62 @@ struct TextureInst {
using TextureInstVector = boost::container::small_vector<TextureInst, 24>; using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
constexpr u32 DESCRIPTOR_SIZE = 8; constexpr u32 DESCRIPTOR_SIZE = 8;
constexpr u32 DESCRIPTOR_SIZE_SHIFT = static_cast<u32>(std::countr_zero(DESCRIPTOR_SIZE)); constexpr u32 DESCRIPTOR_SIZE_SHIFT = u32(std::countr_zero(DESCRIPTOR_SIZE));
constexpr u32 DYNAMIC_DESCRIPTOR_CBUF_BYTES = 16 * 1024; constexpr u32 DESCRIPTOR_MAX_COUNT = 1024;
constexpr u32 MAX_DYNAMIC_DESCRIPTOR_COUNT = 1024;
u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) { u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) {
const IR::Inst* const inst{dynamic_offset.InstRecursive()}; const IR::Inst* const inst = dynamic_offset.InstRecursive();
if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32) { if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32)
return DESCRIPTOR_SIZE_SHIFT; return DESCRIPTOR_SIZE_SHIFT;
} const IR::Value shift = inst->Arg(1);
const IR::Value shift{inst->Arg(1)}; if (!shift.IsImmediate())
if (!shift.IsImmediate()) {
return DESCRIPTOR_SIZE_SHIFT; return DESCRIPTOR_SIZE_SHIFT;
} const u32 size_shift = shift.U32();
const u32 size_shift{shift.U32()}; return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT;
return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift
: DESCRIPTOR_SIZE_SHIFT;
} }
u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift) { u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors) {
if (size_shift >= 31 || base_offset >= DYNAMIC_DESCRIPTOR_CBUF_BYTES) { auto const descriptor_limit = (std::max)(1U, max_descriptors);
auto const max_cbuf_bytes = 16 * descriptor_limit;
if (size_shift >= 31 || base_offset >= max_cbuf_bytes)
return 1; return 1;
} auto const stride = 1U << size_shift;
const u32 stride{1U << size_shift}; auto const available = max_cbuf_bytes - base_offset;
const u32 available{DYNAMIC_DESCRIPTOR_CBUF_BYTES - base_offset}; if (available < DESCRIPTOR_SIZE)
if (available < DESCRIPTOR_SIZE) {
return 1; return 1;
} auto const available_count = 1U + (available - DESCRIPTOR_SIZE) / stride;
const u32 available_count{1U + (available - DESCRIPTOR_SIZE) / stride}; return std::min(descriptor_limit, available_count);
return std::min(MAX_DYNAMIC_DESCRIPTOR_COUNT, available_count);
} }
u32 SaturatingSub(u32 lhs, u32 rhs) { u32 SaturatingSub(u32 lhs, u32 rhs) {
return lhs > rhs ? lhs - rhs : 0; return lhs > rhs ? lhs - rhs : 0;
} }
template <typename Descriptors> template <typename T>
u32 StaticDescriptorCount(const Descriptors& descriptors) { [[nodiscard]] u32 StaticDescriptorCount(T const& descriptors) noexcept {
u32 count{}; return std::accumulate(descriptors.cbegin(), descriptors.cend(), 0U, [](auto const& acc, auto const& e) {
for (const auto& desc : descriptors) { return acc + (e.count <= 1 ? e.count : 0);
if (desc.count <= 1) { });
count += desc.count;
}
}
return count;
} }
u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info, u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info, u32 dynamic_arrays) {
u32 dynamic_arrays) { auto const sampled_limit = (std::max)(1U, std::min(host_info.max_per_stage_descriptor_sampled_images,
if (dynamic_arrays == 0) { host_info.max_descriptor_set_sampled_images));
return MAX_DYNAMIC_DESCRIPTOR_COUNT; auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources);
if (dynamic_arrays > 0) {
auto const sampled_static_count = StaticDescriptorCount(info.texture_buffer_descriptors) + StaticDescriptorCount(info.texture_descriptors);
auto const resource_static_count =
NumDescriptors(info.constant_buffer_descriptors)
+ NumDescriptors(info.storage_buffers_descriptors)
+ sampled_static_count + NumDescriptors(info.image_buffer_descriptors)
+ NumDescriptors(info.image_descriptors);
auto const sampled_budget = SaturatingSub(sampled_limit, sampled_static_count);
auto const resource_budget = SaturatingSub(resource_limit, resource_static_count);
auto const sampled_cap = sampled_budget / dynamic_arrays;
auto const resource_cap = resource_budget / dynamic_arrays;
return (std::max)(1U, (std::min)(sampled_cap, resource_cap));
} }
const u32 sampled_static_count{StaticDescriptorCount(info.texture_buffer_descriptors) + return (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit});
StaticDescriptorCount(info.texture_descriptors)};
const u32 resource_static_count{
NumDescriptors(info.constant_buffer_descriptors) +
NumDescriptors(info.storage_buffers_descriptors) + sampled_static_count +
NumDescriptors(info.image_buffer_descriptors) + NumDescriptors(info.image_descriptors)};
const u32 sampled_limit{std::min(host_info.max_per_stage_descriptor_sampled_images,
host_info.max_descriptor_set_sampled_images)};
const u32 sampled_budget{SaturatingSub(sampled_limit, sampled_static_count)};
const u32 resource_budget{SaturatingSub(host_info.max_per_stage_resources,
resource_static_count)};
const u32 sampled_cap{sampled_budget / dynamic_arrays};
const u32 resource_cap{resource_budget / dynamic_arrays};
return std::max(1U, std::min({MAX_DYNAMIC_DESCRIPTOR_COUNT, sampled_cap, resource_cap}));
} }
IR::Opcode IndexedInstruction(const IR::Inst& inst) { IR::Opcode IndexedInstruction(const IR::Inst& inst) {
@@ -304,21 +295,23 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
} }
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env); std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) { static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info) {
if (const IR::Inst* key = v.InstRecursive()) { if (const IR::Inst* key = v.InstRecursive()) {
if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second; if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second;
auto found = Track(v, env); auto found = Track(v, env, host_info);
if (found) env.track_cache.emplace(key, *found); if (found) env.track_cache.emplace(key, *found);
return found; return found;
} }
return Track(v, env); return Track(v, env, host_info);
} }
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env); std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info);
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env) { std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info) {
return IR::BreadthFirstSearch(value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); }); return IR::BreadthFirstSearch(value, [&env, &host_info](const IR::Inst* inst) {
return TryGetConstBuffer(inst, env, host_info);
});
} }
std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) { std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
@@ -342,13 +335,13 @@ std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
return ReadCbufCached(env, index_number, offset_number); return ReadCbufCached(env, index_number, offset_number);
} }
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env) { std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info) {
switch (inst->GetOpcode()) { switch (inst->GetOpcode()) {
default: default:
return std::nullopt; return std::nullopt;
case IR::Opcode::BitwiseOr32: { case IR::Opcode::BitwiseOr32: {
std::optional lhs{TrackCached(inst->Arg(0), env)}; std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
std::optional rhs{TrackCached(inst->Arg(1), env)}; std::optional rhs{TrackCached(inst->Arg(1), env, host_info)};
if (!lhs || !rhs) { if (!lhs || !rhs) {
return std::nullopt; return std::nullopt;
} }
@@ -378,12 +371,11 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
if (!shift.IsImmediate()) { if (!shift.IsImmediate()) {
return std::nullopt; return std::nullopt;
} }
std::optional lhs{TrackCached(inst->Arg(0), env)}; std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
if (lhs) { if (lhs) {
lhs->shift_left = shift.U32(); lhs->shift_left = shift.U32();
} }
return lhs; return lhs;
break;
} }
case IR::Opcode::BitwiseAnd32: { case IR::Opcode::BitwiseAnd32: {
IR::Value op1{inst->Arg(0)}; IR::Value op1{inst->Arg(0)};
@@ -407,7 +399,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
return std::nullopt; return std::nullopt;
} while (false); } while (false);
} }
std::optional lhs{TrackCached(op1, env)}; std::optional lhs{TrackCached(op1, env, host_info)};
if (lhs) { if (lhs) {
lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32())); lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
} }
@@ -453,7 +445,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
} else { } else {
return std::nullopt; return std::nullopt;
} }
const u32 size_shift{DynamicDescriptorSizeShift(dynamic_offset)}; auto const size_shift = DynamicDescriptorSizeShift(dynamic_offset);
auto const sampled_limit = (std::max)(1U, (std::min)(host_info.max_per_stage_descriptor_sampled_images,
host_info.max_descriptor_set_sampled_images));
auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources);
return ConstBufferAddr{ return ConstBufferAddr{
.index = index.U32(), .index = index.U32(),
.offset = base_offset, .offset = base_offset,
@@ -462,15 +457,15 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
.secondary_offset = 0, .secondary_offset = 0,
.secondary_shift_left = 0, .secondary_shift_left = 0,
.dynamic_offset = dynamic_offset, .dynamic_offset = dynamic_offset,
.count = DynamicDescriptorCount(base_offset, size_shift), .count = DynamicDescriptorCount(base_offset, size_shift, (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit})),
.has_secondary = false, .has_secondary = false,
}; };
} }
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
ConstBufferAddr addr; ConstBufferAddr addr;
if (IsBindless(inst)) { if (IsBindless(inst)) {
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env)}; const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info)};
if (!track_addr) { if (!track_addr) {
throw NotImplementedException("Failed to track bindless texture constant buffer"); throw NotImplementedException("Failed to track bindless texture constant buffer");
@@ -506,15 +501,15 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
return lhs_raw | rhs_raw; return lhs_raw | rhs_raw;
} }
[[maybe_unused]]TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { [[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTextureType(GetTextureHandle(env, cbuf)); return env.ReadTextureType(GetTextureHandle(env, cbuf));
} }
[[maybe_unused]]TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { [[maybe_unused]] TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf)); return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf));
} }
[[maybe_unused]]bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { [[maybe_unused]] bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) {
return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf)); return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf));
} }
@@ -675,7 +670,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
if (!IsTextureInstruction(inst)) { if (!IsTextureInstruction(inst)) {
continue; continue;
} }
to_replace.push_back(MakeInst(env, block, inst)); to_replace.push_back(MakeInst(env, block, inst, host_info));
} }
} }
// Sort instructions to visit textures by constant buffer index, then by offset // Sort instructions to visit textures by constant buffer index, then by offset
@@ -689,8 +684,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
program.info.texture_descriptors, program.info.texture_descriptors,
program.info.image_descriptors, program.info.image_descriptors,
}; };
const u32 sampled_dynamic_cap{ const u32 sampled_dynamic_cap = DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace));
DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace))};
for (TextureInst& texture_inst : to_replace) { for (TextureInst& texture_inst : to_replace) {
// TODO: Handle arrays // TODO: Handle arrays
IR::Inst* const inst{texture_inst.inst}; IR::Inst* const inst{texture_inst.inst};
-1
View File
@@ -92,7 +92,6 @@ struct Profile {
bool has_broken_robust{}; bool has_broken_robust{};
u64 min_ssbo_alignment{}; u64 min_ssbo_alignment{};
u32 max_user_clip_distances{}; u32 max_user_clip_distances{};
}; };
+27 -2
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,12 +18,19 @@ static constexpr auto PERMS = Common::MemoryPermission::ReadWrite;
static constexpr auto HEAP = false; static constexpr auto HEAP = false;
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") { TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } {
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
}
{
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
}
} }
TEST_CASE("HostMemory: Simple map", "[common]") { TEST_CASE("HostMemory: Simple map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP); mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
@@ -30,6 +40,7 @@ TEST_CASE("HostMemory: Simple map", "[common]") {
TEST_CASE("HostMemory: Simple mirror map", "[common]") { TEST_CASE("HostMemory: Simple mirror map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP); mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP);
@@ -41,6 +52,7 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") {
TEST_CASE("HostMemory: Simple unmap", "[common]") { TEST_CASE("HostMemory: Simple unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
@@ -52,6 +64,7 @@ TEST_CASE("HostMemory: Simple unmap", "[common]") {
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
@@ -69,6 +82,7 @@ TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
TEST_CASE("HostMemory: Nieche allocation", "[common]") { TEST_CASE("HostMemory: Nieche allocation", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x0000, 0, 0x20000, PERMS, HEAP); mem.Map(0x0000, 0, 0x20000, PERMS, HEAP);
mem.Unmap(0x0000, 0x4000, HEAP); mem.Unmap(0x0000, 0x4000, HEAP);
mem.Map(0x1000, 0, 0x2000, PERMS, HEAP); mem.Map(0x1000, 0, 0x2000, PERMS, HEAP);
@@ -78,6 +92,7 @@ TEST_CASE("HostMemory: Nieche allocation", "[common]") {
TEST_CASE("HostMemory: Full unmap", "[common]") { TEST_CASE("HostMemory: Full unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x8000, 0x4000, HEAP); mem.Unmap(0x8000, 0x4000, HEAP);
mem.Map(0x6000, 0, 0x16000, PERMS, HEAP); mem.Map(0x6000, 0, 0x16000, PERMS, HEAP);
@@ -85,6 +100,7 @@ TEST_CASE("HostMemory: Full unmap", "[common]") {
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x2000, 0x4000, HEAP); mem.Unmap(0x2000, 0x4000, HEAP);
mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP); mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP);
@@ -92,6 +108,7 @@ TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x6000, 0x4000, HEAP); mem.Unmap(0x6000, 0x4000, HEAP);
mem.Map(0x8000, 0, 0x2000, PERMS, HEAP); mem.Map(0x8000, 0, 0x2000, PERMS, HEAP);
@@ -99,6 +116,7 @@ TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP); mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP);
mem.Unmap(0x3000, 0x1c000, HEAP); mem.Unmap(0x3000, 0x1c000, HEAP);
@@ -107,6 +125,7 @@ TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x2000, 0x4000, HEAP); mem.Unmap(0x2000, 0x4000, HEAP);
@@ -115,6 +134,7 @@ TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
TEST_CASE("HostMemory: Unmap to origin", "[common]") { TEST_CASE("HostMemory: Unmap to origin", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x4000, 0x4000, HEAP); mem.Unmap(0x4000, 0x4000, HEAP);
@@ -124,6 +144,7 @@ TEST_CASE("HostMemory: Unmap to origin", "[common]") {
TEST_CASE("HostMemory: Unmap to right", "[common]") { TEST_CASE("HostMemory: Unmap to right", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x8000, 0x4000, HEAP); mem.Unmap(0x8000, 0x4000, HEAP);
@@ -132,6 +153,7 @@ TEST_CASE("HostMemory: Unmap to right", "[common]") {
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
@@ -144,6 +166,7 @@ TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
@@ -158,6 +181,7 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
@@ -172,6 +196,7 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") { TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
REQUIRE(mem.BackingBasePointer() != nullptr);
mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP); mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP);
mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP); mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP);
+10 -8
View File
@@ -1634,15 +1634,17 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
if (total_size_bytes == 0) { if (total_size_bytes == 0) {
return true; return true;
} }
u64 min_offset = (std::numeric_limits<u64>::max)(); if (Settings::values.enable_gpu_buffer_readback.GetValue()) {
u64 max_offset = 0; u64 min_offset = (std::numeric_limits<u64>::max)();
for (const auto& copy : upload_copies) { u64 max_offset = 0;
min_offset = (std::min)(min_offset, copy.dst_offset); for (const auto& copy : upload_copies) {
max_offset = (std::max)(max_offset, copy.dst_offset + copy.size); min_offset = (std::min)(min_offset, copy.dst_offset);
max_offset = (std::max)(max_offset, copy.dst_offset + copy.size);
}
const DAddr sync_addr = buffer.CpuAddr() + min_offset;
const u64 sync_size = max_offset - min_offset;
DownloadBufferMemory(buffer, sync_addr, sync_size);
} }
const DAddr sync_addr = buffer.CpuAddr() + min_offset;
const u64 sync_size = max_offset - min_offset;
DownloadBufferMemory(buffer, sync_addr, sync_size);
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size()); const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
any_buffer_uploaded = true; any_buffer_uploaded = true;
+40
View File
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/settings.h" #include "common/settings.h"
#include "video_core/dirty_flags.h" #include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_3d.h"
@@ -130,6 +132,44 @@ void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 inde
draw_state.draw_mode = DrawMode::InlineIndex; draw_state.draw_mode = DrawMode::InlineIndex;
} }
void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 method,
const u32* base_start, u32 amount) {
auto& index_buffer = draw_state.inline_index_draw_indexes;
switch (method) {
case MAXWELL3D_REG_INDEX(draw_inline_index): {
const auto* const bytes = reinterpret_cast<const u8*>(base_start);
index_buffer.insert(index_buffer.end(), bytes, bytes + size_t(amount) * sizeof(u32));
break;
}
case MAXWELL3D_REG_INDEX(inline_index_2x16.even): {
const size_t offset = index_buffer.size();
index_buffer.resize(offset + size_t(amount) * 2 * sizeof(u32));
u8* dst = index_buffer.data() + offset;
for (u32 i = 0; i < amount; ++i) {
const u32 word = base_start[i];
const u32 indexes[2]{word & 0xFFFF, word >> 16};
std::memcpy(dst, indexes, sizeof(indexes));
dst += sizeof(indexes);
}
break;
}
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): {
const size_t offset = index_buffer.size();
index_buffer.resize(offset + size_t(amount) * 4 * sizeof(u32));
u8* dst = index_buffer.data() + offset;
for (u32 i = 0; i < amount; ++i) {
const u32 word = base_start[i];
const u32 indexes[4]{word & 0xFF, (word >> 8) & 0xFF, (word >> 16) & 0xFF,
word >> 24};
std::memcpy(dst, indexes, sizeof(indexes));
dst += sizeof(indexes);
}
break;
}
}
draw_state.draw_mode = DrawMode::InlineIndex;
}
void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) { void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) {
auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First; auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent; auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
+17
View File
@@ -436,6 +436,14 @@ void Maxwell3D::CallMultiMethod(Core::System& system, u32 method, const u32* bas
upload_state.ProcessData(base_start, amount); upload_state.ProcessData(base_start, amount);
return; return;
} }
case MAXWELL3D_REG_INDEX(draw_inline_index):
case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
if (shadow_state.shadow_ram_control != Regs::ShadowRamControl::Replay) {
ProcessInlineIndexMultiData(method, base_start, amount);
break;
}
[[fallthrough]];
default: default:
for (u32 i = 0; i < amount; i++) { for (u32 i = 0; i < amount; i++) {
CallMethod(system, method, base_start[i], methods_pending - i <= 1); CallMethod(system, method, base_start[i], methods_pending - i <= 1);
@@ -622,6 +630,15 @@ void Maxwell3D::ProcessCBData(u32 value) {
ProcessCBMultiData(&value, 1); ProcessCBMultiData(&value, 1);
} }
void Maxwell3D::ProcessInlineIndexMultiData(u32 method, const u32* start_base, u32 amount) {
if (amount == 0) {
return;
}
const u32 argument = ProcessShadowRam(method, start_base[amount - 1]);
ProcessDirtyRegisters(method, argument);
draw_manager.SetInlineIndexBuffer(*this, method, start_base, amount);
}
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
const GPUVAddr tic_address_gpu{regs.tex_header.Address() + const GPUVAddr tic_address_gpu{regs.tex_header.Address() +
tic_index * sizeof(Texture::TICEntry)}; tic_index * sizeof(Texture::TICEntry)};
+3
View File
@@ -3077,6 +3077,7 @@ public:
void DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology); void DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology);
void DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count); void DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count);
void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index); void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index);
void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 method, const u32* base_start, u32 amount);
void DrawBegin(Maxwell3D& maxwell3d); void DrawBegin(Maxwell3D& maxwell3d);
void DrawEnd(Maxwell3D& maxwell3d, u32 instance_count = 1, bool force_draw = false); void DrawEnd(Maxwell3D& maxwell3d, u32 instance_count = 1, bool force_draw = false);
void DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument); void DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument);
@@ -3193,6 +3194,8 @@ public:
void ProcessCBData(u32 value); void ProcessCBData(u32 value);
void ProcessCBMultiData(const u32* start_base, u32 amount); void ProcessCBMultiData(const u32* start_base, u32 amount);
void ProcessInlineIndexMultiData(u32 method, const u32* start_base, u32 amount);
private: private:
void InitializeRegisterDefaults(); void InitializeRegisterDefaults();
+29 -35
View File
@@ -4,6 +4,7 @@
#include "video_core/gpu_logging/gpu_logging.h" #include "video_core/gpu_logging/gpu_logging.h"
#include <fmt/format.h> #include <fmt/format.h>
#include <mutex>
#include <thread> #include <thread>
#include "common/fs/file.h" #include "common/fs/file.h"
@@ -280,13 +281,12 @@ void GPULogger::LogMemoryDeallocation(uintptr_t memory) {
} }
void GPULogger::LogShaderCompilation(const std::string& shader_name, void GPULogger::LogShaderCompilation(const std::string& shader_name,
const std::string& shader_info, const std::string& shader_info) {
std::span<const u32> spirv_code) {
if (!initialized || current_level == LogLevel::Off) { if (!initialized || current_level == LogLevel::Off) {
return; return;
} }
if (!dump_shaders && current_level < LogLevel::Verbose) { if (current_level < LogLevel::Verbose) {
return; return;
} }
@@ -294,38 +294,36 @@ void GPULogger::LogShaderCompilation(const std::string& shader_name,
std::chrono::steady_clock::now().time_since_epoch()); std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n", const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n",
FormatTimestamp(timestamp), shader_name, shader_info); FormatTimestamp(timestamp), shader_name, shader_info);
WriteToLog(log_entry); WriteToLog(log_entry);
}
// Dump SPIR-V binary if enabled and we have data bool IsActive() noexcept {
if (dump_shaders && !spirv_code.empty()) { return Settings::values.gpu_log_level.GetValue() != Settings::GpuLogLevel::Off;
using namespace Common::FS; }
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
const auto shaders_dir = log_dir / "shaders";
// Create directory on first dump void DumpSpirvShader(u64 shader_hash, std::span<const u32> spirv_code) {
if (!shader_dump_dir_created) { if (spirv_code.empty()) {
[[maybe_unused]] const bool created = CreateDir(shaders_dir); return;
shader_dump_dir_created = true;
}
// Write SPIR-V binary file
const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name);
auto shader_file = std::make_unique<Common::FS::IOFile>(
shader_path, FileAccessMode::Write, FileType::BinaryFile);
if (shader_file->IsOpen()) {
const size_t bytes_to_write = spirv_code.size() * sizeof(u32);
static_cast<void>(shader_file->WriteSpan(spirv_code));
shader_file->Close();
const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n",
FormatTimestamp(timestamp), shader_path.string(), bytes_to_write);
WriteToLog(dump_log);
} else {
LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string());
}
} }
using namespace Common::FS;
const auto& dump_dir = GetEdenPath(EdenPath::DumpDir);
// Ensure DumpDir exists once. CreateDir is idempotent, so guarded to skip the syscall.
static std::once_flag dump_dir_flag;
std::call_once(dump_dir_flag, [&dump_dir]() {
[[maybe_unused]] const bool created = CreateDir(dump_dir);
});
const auto shader_path = dump_dir / fmt::format("{:016x}_{:016x}.spv",
Settings::GetCurrentProgramID(), shader_hash);
Common::FS::IOFile shader_file(shader_path, FileAccessMode::Write, FileType::BinaryFile);
if (!shader_file.IsOpen()) {
LOG_WARNING(Render_Vulkan, "[Shader Dump] Failed to open {}", shader_path.string());
return;
}
static_cast<void>(shader_file.WriteSpan(spirv_code));
} }
void GPULogger::LogPipelineStateChange(const std::string& state_info) { void GPULogger::LogPipelineStateChange(const std::string& state_info) {
@@ -657,10 +655,6 @@ void GPULogger::EnableVulkanCallTracking(bool enabled) {
track_vulkan_calls = enabled; track_vulkan_calls = enabled;
} }
void GPULogger::EnableShaderDumps(bool enabled) {
dump_shaders = enabled;
}
void GPULogger::EnableMemoryTracking(bool enabled) { void GPULogger::EnableMemoryTracking(bool enabled) {
track_memory = enabled; track_memory = enabled;
} }
+5 -7
View File
@@ -87,8 +87,7 @@ public:
void LogVulkanCall(const std::string& call_name, const std::string& params, int result); void LogVulkanCall(const std::string& call_name, const std::string& params, int result);
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags); void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags);
void LogMemoryDeallocation(uintptr_t memory); void LogMemoryDeallocation(uintptr_t memory);
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info, void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info);
std::span<const u32> spirv_code = {});
void LogPipelineStateChange(const std::string& state_info); void LogPipelineStateChange(const std::string& state_info);
void LogDriverDebugInfo(const std::string& debug_info); void LogDriverDebugInfo(const std::string& debug_info);
@@ -121,7 +120,6 @@ public:
// Settings // Settings
void SetLogLevel(LogLevel level); void SetLogLevel(LogLevel level);
void EnableVulkanCallTracking(bool enabled); void EnableVulkanCallTracking(bool enabled);
void EnableShaderDumps(bool enabled);
void EnableMemoryTracking(bool enabled); void EnableMemoryTracking(bool enabled);
void EnableDriverDebugInfo(bool enabled); void EnableDriverDebugInfo(bool enabled);
void SetRingBufferSize(size_t entries); void SetRingBufferSize(size_t entries);
@@ -171,7 +169,6 @@ private:
// Feature flags // Feature flags
bool track_vulkan_calls = true; bool track_vulkan_calls = true;
bool dump_shaders = false;
bool track_memory = false; bool track_memory = false;
bool capture_driver_debug = false; bool capture_driver_debug = false;
@@ -179,15 +176,16 @@ private:
std::set<std::string> used_extensions; std::set<std::string> used_extensions;
mutable std::mutex extension_mutex; mutable std::mutex extension_mutex;
// Shader dump directory (created on demand)
bool shader_dump_dir_created = false;
// Stored state for crash dumps // Stored state for crash dumps
std::string stored_driver_debug_info; std::string stored_driver_debug_info;
std::string stored_pipeline_state; std::string stored_pipeline_state;
mutable std::mutex state_mutex; mutable std::mutex state_mutex;
}; };
[[nodiscard]] bool IsActive() noexcept;
void DumpSpirvShader(u64 shader_hash, std::span<const u32> spirv_code);
// Helper to get stage name from index // Helper to get stage name from index
inline const char* GetShaderStageName(size_t stage_index) { inline const char* GetShaderStageName(size_t stage_index) {
static constexpr std::array<const char*, 5> stage_names{ static constexpr std::array<const char*, 5> stage_names{
@@ -11,9 +11,8 @@
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
#define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 0
#define BINDING_INPUT_BUFFER 1 #define BINDING_OUTPUT_IMAGE 1
#define BINDING_OUTPUT_IMAGE 2
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
@@ -26,8 +25,7 @@
#define BEGIN_PUSH_CONSTANTS #define BEGIN_PUSH_CONSTANTS
#define END_PUSH_CONSTANTS #define END_PUSH_CONSTANTS
#define UNIFORM(n) layout (location = n) uniform #define UNIFORM(n) layout (location = n) uniform
#define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 0
#define BINDING_INPUT_BUFFER 1
#define BINDING_OUTPUT_IMAGE 0 #define BINDING_OUTPUT_IMAGE 0
#endif #endif
@@ -43,10 +41,6 @@ UNIFORM(6) uint block_height;
UNIFORM(7) uint block_height_mask; UNIFORM(7) uint block_height_mask;
END_PUSH_CONSTANTS END_PUSH_CONSTANTS
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
uint swizzle_table[];
};
#if HAS_EXTENDED_TYPES #if HAS_EXTENDED_TYPES
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
@@ -71,9 +65,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) { uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK; pos = pos & SWIZZLE_MASK;
return swizzle_table[pos.y * 64 + pos.x]; return SwizzleTable(pos.y * 64 + pos.x);
} }
uvec4 ReadTexel(uint offset) { uvec4 ReadTexel(uint offset) {
@@ -11,9 +11,8 @@
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
#define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 0
#define BINDING_INPUT_BUFFER 1 #define BINDING_OUTPUT_IMAGE 1
#define BINDING_OUTPUT_IMAGE 2
#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
@@ -26,8 +25,7 @@
#define BEGIN_PUSH_CONSTANTS #define BEGIN_PUSH_CONSTANTS
#define END_PUSH_CONSTANTS #define END_PUSH_CONSTANTS
#define UNIFORM(n) layout (location = n) uniform #define UNIFORM(n) layout (location = n) uniform
#define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 0
#define BINDING_INPUT_BUFFER 1
#define BINDING_OUTPUT_IMAGE 0 #define BINDING_OUTPUT_IMAGE 0
#endif #endif
@@ -45,10 +43,6 @@ UNIFORM(8) uint block_depth;
UNIFORM(9) uint block_depth_mask; UNIFORM(9) uint block_depth_mask;
END_PUSH_CONSTANTS END_PUSH_CONSTANTS
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
uint swizzle_table[];
};
#if HAS_EXTENDED_TYPES #if HAS_EXTENDED_TYPES
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
@@ -73,9 +67,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) { uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK; pos = pos & SWIZZLE_MASK;
return swizzle_table[pos.y * 64 + pos.x]; return SwizzleTable(pos.y * 64 + pos.x);
} }
uvec4 ReadTexel(uint offset) { uvec4 ReadTexel(uint offset) {
@@ -10,9 +10,8 @@
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
#define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 0
#define BINDING_INPUT_BUFFER 1 #define BINDING_OUTPUT_BUFFER 1
#define BINDING_OUTPUT_BUFFER 2
#else #else
#extension GL_NV_gpu_shader5 : enable #extension GL_NV_gpu_shader5 : enable
#ifdef GL_NV_gpu_shader5 #ifdef GL_NV_gpu_shader5
@@ -23,7 +22,6 @@
#define BEGIN_PUSH_CONSTANTS #define BEGIN_PUSH_CONSTANTS
#define END_PUSH_CONSTANTS #define END_PUSH_CONSTANTS
#define UNIFORM(n) layout(location = n) uniform #define UNIFORM(n) layout(location = n) uniform
#define BINDING_SWIZZLE_BUFFER 0
#define BINDING_INPUT_BUFFER 1 #define BINDING_INPUT_BUFFER 1
#define BINDING_OUTPUT_BUFFER 0 #define BINDING_OUTPUT_BUFFER 0
#endif #endif
@@ -66,13 +64,9 @@ END_PUSH_CONSTANTS
#endif #endif
// --- Buffers --- // --- Buffers ---
layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
uint swizzle_table[];
};
#if HAS_EXTENDED_TYPES #if HAS_EXTENDED_TYPES
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
#endif #endif
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
@@ -96,10 +90,20 @@ const uint GOB_SIZE_Z_SHIFT = 0;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u); const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
// --- Helpers --- // --- Helpers ---
uint SwizzleOffset(uvec2 pos) { uint SwizzleOffset(uvec2 pos) {
pos &= SWIZZLE_MASK; pos &= SWIZZLE_MASK;
return swizzle_table[pos.y * 64u + pos.x]; return SwizzleTable(pos.y * 64u + pos.x);
} }
uvec4 ReadTexel(uint offset) { uvec4 ReadTexel(uint offset) {
+6 -14
View File
@@ -1328,22 +1328,14 @@ Macro::Opcode MacroJITx64Impl::GetOpCode() const {
#endif #endif
static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) { static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
const auto base_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; const auto dump_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)};
const auto macro_dir{base_dir / "macros"}; if (!Common::FS::CreateDir(dump_dir)) {
if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { LOG_ERROR(Common_Filesystem, "Failed to create dump directory");
LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories");
return; return;
} }
auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; const char* const variant_suffix = decompiled ? "jit" : "raw";
const auto name{dump_dir / fmt::format("{:016x}_{:016x}_{}.macro",
if (decompiled) { Settings::GetCurrentProgramID(), hash, variant_suffix)};
auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)};
if (Common::FS::Exists(name)) {
(void)Common::FS::RenameFile(name, new_name);
return;
}
name = new_name;
}
std::fstream macro_file(name, std::ios::out | std::ios::binary); std::fstream macro_file(name, std::ios::out | std::ios::binary);
if (!macro_file) { if (!macro_file) {
@@ -245,16 +245,31 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
std::min<u32>(device.GetMaxUserClipDistances(), Maxwell::Regs::NumClipDistances), std::min<u32>(device.GetMaxUserClipDistances(), Maxwell::Regs::NumClipDistances),
}, },
host_info{ host_info{
.support_float64 = true, .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
.support_float16 = false, .max_per_stage_descriptor_sampled_images =
.support_int64 = device.HasShaderInt64(), Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.needs_demote_reorder = device.IsAmd(), .max_per_stage_resources = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.support_snorm_render_buffer = false, .max_descriptor_set_samplers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.support_viewport_index_layer = device.HasVertexViewportLayer(), .max_descriptor_set_uniform_buffers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()), .max_descriptor_set_uniform_buffers_dynamic =
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.support_conditional_barrier = device.SupportsConditionalBarriers(), .max_descriptor_set_storage_buffers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.max_descriptor_set_storage_buffers_dynamic =
Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.max_descriptor_set_sampled_images = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.max_descriptor_set_storage_images = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.max_descriptor_set_input_attachements =
Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT,
.support_float64 = true,
.support_float16 = false,
.support_int64 = device.HasShaderInt64(),
.needs_demote_reorder = device.IsAmd(),
.support_snorm_render_buffer = false,
.support_viewport_index_layer = device.HasVertexViewportLayer(),
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
} { } {
host_info.ApplyDescriptorLimitPolicy();
if (use_asynchronous_shaders) { if (use_asynchronous_shaders) {
workers = CreateWorkers(); workers = CreateWorkers();
} }
@@ -481,7 +496,7 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
const u32 cfg_offset = u32(env.StartAddress() + sizeof(Shader::ProgramHeader)); const u32 cfg_offset = u32(env.StartAddress() + sizeof(Shader::ProgramHeader));
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
if (Settings::values.dump_shaders) { if (Settings::values.dump_guest_shaders) {
env.Dump(hash, key.unique_hashes[index]); env.Dump(hash, key.unique_hashes[index]);
} }
@@ -578,7 +593,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
if (Settings::values.dump_shaders) { if (Settings::values.dump_guest_shaders) {
env.Dump(hash, key.unique_hash); env.Dump(hash, key.unique_hash);
} }
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -56,10 +59,8 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)
copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)), copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)),
convert_s8d24_program(MakeProgram(OPENGL_CONVERT_S8D24_COMP)), convert_s8d24_program(MakeProgram(OPENGL_CONVERT_S8D24_COMP)),
convert_ms_to_nonms_program(MakeProgram(CONVERT_MSAA_TO_NON_MSAA_COMP)), convert_ms_to_nonms_program(MakeProgram(CONVERT_MSAA_TO_NON_MSAA_COMP)),
convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP)) { convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP))
const auto swizzle_table = Tegra::Texture::MakeSwizzleTable(); {
swizzle_table_buffer.Create();
glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0);
} }
UtilShaders::~UtilShaders() = default; UtilShaders::~UtilShaders() = default;
@@ -116,13 +117,11 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map,
void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map, void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
std::span<const SwizzleParameters> swizzles) { std::span<const SwizzleParameters> swizzles) {
static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; static constexpr GLuint BINDING_INPUT_BUFFER = 0;
static constexpr GLuint BINDING_INPUT_BUFFER = 1;
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
program_manager.BindComputeProgram(block_linear_unswizzle_2d_program.handle); program_manager.BindComputeProgram(block_linear_unswizzle_2d_program.handle);
glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
for (const SwizzleParameters& swizzle : swizzles) { for (const SwizzleParameters& swizzle : swizzles) {
@@ -153,14 +152,11 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map,
void UtilShaders::BlockLinearUpload3D(Image& image, const StagingBufferMap& map, void UtilShaders::BlockLinearUpload3D(Image& image, const StagingBufferMap& map,
std::span<const SwizzleParameters> swizzles) { std::span<const SwizzleParameters> swizzles) {
static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8}; static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8};
static constexpr GLuint BINDING_INPUT_BUFFER = 0;
static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
static constexpr GLuint BINDING_INPUT_BUFFER = 1;
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
program_manager.BindComputeProgram(block_linear_unswizzle_3d_program.handle); program_manager.BindComputeProgram(block_linear_unswizzle_3d_program.handle);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
for (const SwizzleParameters& swizzle : swizzles) { for (const SwizzleParameters& swizzle : swizzles) {
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -45,9 +48,6 @@ public:
private: private:
ProgramManager& program_manager; ProgramManager& program_manager;
OGLBuffer swizzle_table_buffer;
OGLProgram astc_decoder_program; OGLProgram astc_decoder_program;
OGLProgram block_linear_unswizzle_2d_program; OGLProgram block_linear_unswizzle_2d_program;
OGLProgram block_linear_unswizzle_3d_program; OGLProgram block_linear_unswizzle_3d_program;
@@ -22,6 +22,15 @@ namespace Vulkan {
using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
[[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) {
return Shader::NumDescriptors(info.constant_buffer_descriptors) +
Shader::NumDescriptors(info.storage_buffers_descriptors) +
Shader::NumDescriptors(info.texture_buffer_descriptors) +
Shader::NumDescriptors(info.image_buffer_descriptors) +
Shader::NumDescriptors(info.texture_descriptors) +
Shader::NumDescriptors(info.image_descriptors);
}
class DescriptorLayoutBuilder { class DescriptorLayoutBuilder {
public: public:
DescriptorLayoutBuilder(const Device& device_) : device{&device_} {} DescriptorLayoutBuilder(const Device& device_) : device{&device_} {}
@@ -194,7 +203,11 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VideoCommon::ImageViewId image_view_id{(views++)->id}; const VideoCommon::ImageViewId image_view_id{(views++)->id};
const VideoCommon::SamplerId sampler_id{*(samplers++)}; const VideoCommon::SamplerId sampler_id{*(samplers++)};
ImageView& image_view{texture_cache.GetImageView(image_view_id)}; ImageView& image_view{texture_cache.GetImageView(image_view_id)};
const VkImageView vk_image_view{image_view.Handle(desc.type)}; VkImageView vk_image_view{image_view.Handle(desc.type)};
if (vk_image_view == VK_NULL_HANDLE) {
const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)};
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
}
const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()}; !image_view.SupportsAnisotropy()};
@@ -169,6 +169,7 @@ try
} }
RendererVulkan::~RendererVulkan() { RendererVulkan::~RendererVulkan() {
scheduler.WaitWorker();
scheduler.RegisterOnSubmit([] {}); scheduler.RegisterOnSubmit([] {});
void(device.GetLogical().WaitIdle()); void(device.GetLogical().WaitIdle());
} }
@@ -25,6 +25,9 @@
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h"
#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -232,6 +235,26 @@ struct QueriesPrefixScanPushConstants {
struct ConditionalRenderingResolvePushConstants { struct ConditionalRenderingResolvePushConstants {
u32 compare_to_zero; u32 compare_to_zero;
}; };
struct BlockLinear3DImagePushConstants {
alignas(16) std::array<u32, 3> origin;
alignas(16) std::array<s32, 3> destination;
u32 bytes_per_block_log2;
u32 slice_size;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
u32 block_depth;
u32 block_depth_mask;
};
struct PitchUnswizzlePushConstants {
std::array<u32, 2> origin;
std::array<s32, 2> destination;
u32 bytes_per_block;
u32 pitch;
};
} // Anonymous namespace } // Anonymous namespace
ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool,
@@ -326,7 +349,7 @@ std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer
const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16)); const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
@@ -384,7 +407,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
const std::size_t staging_size = num_tri_vertices * sizeof(u32); const std::size_t staging_size = num_tri_vertices * sizeof(u32);
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
@@ -429,7 +452,7 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
} }
const size_t compare_size = compare_to_zero ? 8 : 24; const size_t compare_size = compare_to_zero ? 8 : 24;
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size);
compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32)); compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
@@ -498,7 +521,7 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
static constexpr size_t DISPATCH_SIZE = 2048U; static constexpr size_t DISPATCH_SIZE = 2048U;
size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE); size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE);
current_runs -= runs_to_do; current_runs -= runs_to_do;
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 3);
compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64)); compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64));
compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64)); compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64));
compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64)); compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64));
@@ -600,7 +623,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
const u32 num_dispatches_z = image.info.resources.layers; const u32 num_dispatches_z = image.info.resources.layers;
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
image.guest_size_bytes - swizzle.buffer_offset); image.guest_size_bytes - swizzle.buffer_offset);
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
@@ -653,71 +676,311 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
scheduler.Finish(); scheduler.Finish();
} }
constexpr u32 BL3D_BINDING_SWIZZLE_TABLE = 0; BlockLinearUnswizzleImage2DPass::BlockLinearUnswizzleImage2DPass(
constexpr u32 BL3D_BINDING_INPUT_BUFFER = 1; const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 2; StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS,
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearSwizzle2DParams)>,
BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
constexpr std::array<VkDescriptorSetLayoutBinding, 3> BL3D_DESCRIPTOR_SET_BINDINGS{{ BlockLinearUnswizzleImage2DPass::~BlockLinearUnswizzleImage2DPass() = default;
{
.binding = BL3D_BINDING_SWIZZLE_TABLE,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // swizzle_table[]
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3D_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3D_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr DescriptorBankInfo BL3D_BANK_INFO{ void BlockLinearUnswizzleImage2DPass::Unswizzle(
.uniform_buffers = 0, Image& image, const StagingBufferRef& map,
.storage_buffers = 3, std::span<const VideoCommon::SwizzleParameters> swizzles) {
.texture_buffers = 0, using namespace VideoCommon::Accelerated;
.image_buffers = 0, scheduler.RequestOutsideRenderPassOperationContext();
.textures = 0, const VkPipeline vk_pipeline = *pipeline;
.images = 0, const VkImageAspectFlags aspect_mask = image.AspectMask();
.score = 3, const VkImage vk_image = image.Handle();
}; const bool is_initialized = image.ExchangeInitialization();
scheduler.Record([vk_pipeline, vk_image, aspect_mask,
is_initialized](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT
: VK_ACCESS_NONE),
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER
: VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
});
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
const u32 num_dispatches_z = image.info.resources.layers;
constexpr std::array<VkDescriptorUpdateTemplateEntry, 3> compute_pass_descriptor_queue.Acquire(scheduler, 2);
BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
{ image.guest_size_bytes - swizzle.buffer_offset);
.dstBinding = BL3D_BINDING_SWIZZLE_TABLE, compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
.dstArrayElement = 0, const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
.offset = BL3D_BINDING_SWIZZLE_TABLE * sizeof(DescriptorUpdateEntry), scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params,
.stride = sizeof(DescriptorUpdateEntry), descriptor_data](vk::CommandBuffer cmdbuf) {
}, const VkDescriptorSet set = descriptor_allocator.Commit();
{ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
.dstBinding = BL3D_BINDING_INPUT_BUFFER, cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
.dstArrayElement = 0, cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
.descriptorCount = 1, cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z);
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, });
.offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), }
.stride = sizeof(DescriptorUpdateEntry), scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) {
}, const VkImageMemoryBarrier image_barrier{
{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.dstBinding = BL3D_BINDING_OUTPUT_BUFFER, .pNext = nullptr,
.dstArrayElement = 0, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.descriptorCount = 1, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry), .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.stride = sizeof(DescriptorUpdateEntry), .newLayout = VK_IMAGE_LAYOUT_GENERAL,
} .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
}}; .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier);
});
scheduler.Finish();
}
BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS,
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinear3DImagePushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default;
void BlockLinearUnswizzleImage3DPass::Unswizzle(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
using namespace VideoCommon::Accelerated;
scheduler.RequestOutsideRenderPassOperationContext();
const VkPipeline vk_pipeline = *pipeline;
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
scheduler.Record([vk_pipeline, vk_image, aspect_mask,
is_initialized](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT
: VK_ACCESS_NONE),
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER
: VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
});
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U);
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U);
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
image.guest_size_bytes - swizzle.buffer_offset);
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
const auto p = MakeBlockLinearSwizzle3DParams(swizzle, image.info);
const BlockLinear3DImagePushConstants params{
.origin = p.origin,
.destination = p.destination,
.bytes_per_block_log2 = p.bytes_per_block_log2,
.slice_size = p.slice_size,
.block_size = p.block_size,
.x_shift = p.x_shift,
.block_height = p.block_height,
.block_height_mask = p.block_height_mask,
.block_depth = p.block_depth,
.block_depth_mask = p.block_depth_mask,
};
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params,
descriptor_data](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z);
});
}
scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier);
});
scheduler.Finish();
}
PitchUnswizzlePass::PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS,
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(PitchUnswizzlePushConstants)>,
PITCH_UNSWIZZLE_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
PitchUnswizzlePass::~PitchUnswizzlePass() = default;
void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
scheduler.RequestOutsideRenderPassOperationContext();
const VkPipeline vk_pipeline = *pipeline;
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
scheduler.Record([vk_pipeline, vk_image, aspect_mask,
is_initialized](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT
: VK_ACCESS_NONE),
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER
: VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
});
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format);
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
image.guest_size_bytes - swizzle.buffer_offset);
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
const PitchUnswizzlePushConstants params{
.origin = {0, 0},
.destination = {0, 0},
.bytes_per_block = bytes_per_block,
.pitch = image.info.pitch,
};
scheduler.Record([this, num_dispatches_x, num_dispatches_y, params,
descriptor_data](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1);
});
}
scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier);
});
scheduler.Finish();
}
constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle3DPushConstants { struct alignas(16) BlockLinearUnswizzle3DPushConstants {
u32 blocks_dim[3]; // Offset 0 u32 blocks_dim[3]; // Offset 0
@@ -745,11 +1008,50 @@ BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass(
DescriptorPool& descriptor_pool_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_, StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_) ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass( : ComputePass(device_, scheduler_, descriptor_pool_,
device_, scheduler_, descriptor_pool_, std::array<VkDescriptorSetLayoutBinding, 2>{{
BL3D_DESCRIPTOR_SET_BINDINGS, {
BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, .binding = BL3D_BINDING_INPUT_BUFFER,
BL3D_BANK_INFO, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3D_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}},
std::array<VkDescriptorUpdateTemplateEntry, 2>{{
{
.dstBinding = BL3D_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL3D_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
}
}},
DescriptorBankInfo{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
},
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DPushConstants)>, COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_3D_BCN_COMP_SPV), BLOCK_LINEAR_UNSWIZZLE_3D_BCN_COMP_SPV),
scheduler{scheduler_}, scheduler{scheduler_},
@@ -821,9 +1123,7 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
pc.blocks_dim[1] = blocks_y; pc.blocks_dim[1] = blocks_y;
pc.blocks_dim[2] = z_count; // Only process the count pc.blocks_dim[2] = z_count; // Only process the count
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 3);
compute_pass_descriptor_queue.AddBuffer(*image.runtime->swizzle_table_buffer, 0,
image.runtime->swizzle_table_size);
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, compute_pass_descriptor_queue.AddBuffer(swizzled.buffer,
sw.buffer_offset + swizzled.offset, sw.buffer_offset + swizzled.offset,
image.guest_size_bytes - sw.buffer_offset); image.guest_size_bytes - sw.buffer_offset);
@@ -859,6 +1159,23 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
return; return;
} }
if (!is_first_chunk) {
const VkBufferMemoryBarrier reuse_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, nullptr,
reuse_barrier, nullptr);
}
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
@@ -989,7 +1306,7 @@ void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
ASSERT(copy.dst_subresource.base_layer == 0); ASSERT(copy.dst_subresource.base_layer == 0);
ASSERT(copy.dst_subresource.num_layers == 1); ASSERT(copy.dst_subresource.num_layers == 1);
compute_pass_descriptor_queue.Acquire(); compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddImage( compute_pass_descriptor_queue.AddImage(
src_image.StorageImageView(copy.src_subresource.base_level)); src_image.StorageImageView(copy.src_subresource.base_level));
compute_pass_descriptor_queue.AddImage( compute_pass_descriptor_queue.AddImage(
@@ -25,6 +25,7 @@ struct SwizzleParameters;
namespace Vulkan { namespace Vulkan {
using VideoCommon::Accelerated::BlockLinearSwizzle2DParams;
using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; using VideoCommon::Accelerated::BlockLinearSwizzle3DParams;
class Device; class Device;
@@ -164,6 +165,56 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue; ComputePassDescriptorQueue& compute_pass_descriptor_queue;
}; };
class BlockLinearUnswizzleImage2DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzleImage2DPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzleImage2DPass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzleImage3DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzleImage3DPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzleImage3DPass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class PitchUnswizzlePass final : public ComputePass {
public:
explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~PitchUnswizzlePass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class MSAACopyPass final : public ComputePass { class MSAACopyPass final : public ComputePass {
public: public:
@@ -45,6 +45,7 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
} }
std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(),
uniform_buffer_sizes.begin()); uniform_buffer_sizes.begin());
num_descriptor_entries = NumDescriptorEntries(info);
auto func{[this, &scheduler, &descriptor_pool, shader_notify, pipeline_statistics] { auto func{[this, &scheduler, &descriptor_pool, shader_notify, pipeline_statistics] {
DescriptorLayoutBuilder builder{device}; DescriptorLayoutBuilder builder{device};
@@ -87,7 +88,7 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
}, *pipeline_cache); }, *pipeline_cache);
// Log compute pipeline creation // Log compute pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) { if (GPU::Logging::IsActive()) {
GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange( GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(
"ComputePipeline created" "ComputePipeline created"
); );
@@ -113,7 +114,7 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk
void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, Tegra::MemoryManager& gpu_memory, Scheduler& scheduler,
BufferCache& buffer_cache, TextureCache& texture_cache) { BufferCache& buffer_cache, TextureCache& texture_cache) {
guest_descriptor_queue.Acquire(); guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes); buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes);
buffer_cache.UnbindComputeStorageBuffers(); buffer_cache.UnbindComputeStorageBuffers();
@@ -223,7 +224,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
} }
// Log compute pipeline binding // Log compute pipeline binding
if (Settings::values.gpu_logging_enabled.GetValue() && if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_vulkan_calls.GetValue()) { Settings::values.gpu_log_vulkan_calls.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline"); GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline");
} }
@@ -53,6 +53,7 @@ private:
vk::PipelineCache& pipeline_cache; vk::PipelineCache& pipeline_cache;
GuestDescriptorQueue& guest_descriptor_queue; GuestDescriptorQueue& guest_descriptor_queue;
Shader::Info info; Shader::Info info;
u32 num_descriptor_entries{};
VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{}; VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{};
@@ -268,6 +268,7 @@ GraphicsPipeline::GraphicsPipeline(
num_textures += Shader::NumDescriptors(info->texture_descriptors); num_textures += Shader::NumDescriptors(info->texture_descriptors);
num_image_elements += Shader::NumDescriptors(info->texture_descriptors); num_image_elements += Shader::NumDescriptors(info->texture_descriptors);
num_image_elements += Shader::NumDescriptors(info->image_descriptors); num_image_elements += Shader::NumDescriptors(info->image_descriptors);
num_descriptor_entries += NumDescriptorEntries(*info);
} }
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0]; fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] { auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] {
@@ -473,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.UpdateGraphicsBuffers(is_indexed); buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed); buffer_cache.BindHostGeometryBuffers(is_indexed);
guest_descriptor_queue.Acquire(); guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
RescalingPushConstant rescaling; RescalingPushConstant rescaling;
RenderAreaPushConstant render_area; RenderAreaPushConstant render_area;
@@ -532,7 +533,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)}; const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
// Log graphics pipeline binding // Log graphics pipeline binding
if (bind_pipeline && Settings::values.gpu_logging_enabled.GetValue() && if (bind_pipeline && GPU::Logging::IsActive() &&
Settings::values.gpu_log_vulkan_calls.GetValue()) { Settings::values.gpu_log_vulkan_calls.GetValue()) {
const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash()); const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash());
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info); GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info);
@@ -986,7 +987,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
}, *pipeline_cache); }, *pipeline_cache);
// Log graphics pipeline creation // Log graphics pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) { if (GPU::Logging::IsActive()) {
const std::string pipeline_info = fmt::format( const std::string pipeline_info = fmt::format(
"GraphicsPipeline created: stages={}, attachments={}", "GraphicsPipeline created: stages={}, attachments={}",
shader_stages.size(), shader_stages.size(),
@@ -159,6 +159,7 @@ private:
std::array<Shader::Info, NUM_STAGES> stage_infos; std::array<Shader::Info, NUM_STAGES> stage_infos;
std::array<u32, 5> enabled_uniform_buffer_masks{}; std::array<u32, 5> enabled_uniform_buffer_masks{};
VideoCommon::UniformBufferSizes uniform_buffer_sizes{}; VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
u32 num_descriptor_entries{};
size_t num_image_elements{}; size_t num_image_elements{};
u32 num_textures{}; u32 num_textures{};
bool fragment_has_color0_output{}; bool fragment_has_color0_output{};

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