Compare commits

...
151 Commits
Author SHA1 Message Date
Frog Business 2ba2f75bce Wiimote to GunCon3 (#18154)
So I could play Deadstorm Pirates 2 players with DolphinBar + 2 Wiimotes
99% of this is AI assisted development.
I reviewed the code and the functionality.
- There is this logic where you might find it weird.
It maps first available player with GunCon3 to slot 0.
So if you have P4:G and P6:G, you'll have P4:0, P6:1
This is intentional because I couldn't figure out why P2 is reserved and
I needed to map to P1 and P2.
- Has a GUI so you can see your Wiimotes and remap them.
- Enabling PS Move cursor overlay will also show GunCon3 IRs on the
screen.

### NOTE

On Linux you'll need udev rules (same as Dolphin) for this to work. 
Put [THIS
FILE](https://github.com/dolphin-emu/dolphin/blob/master/Data/51-usb-device.rules)
in `/etc/udev/rules.d/`
2026-10-11 10:01:57 +00:00
Megamouse 26098fb42d Qt: Always detect app icon based on emulator variables 2026-10-11 10:56:06 +02:00
Sean Brar d1d0cbccea Savestates: Fix saving in-memory file descriptors 2026-10-11 09:55:22 +03:00
yahfz 7b4de2f13d PPU Loader: Skip ppc_seg mapping during virtual loads 2026-10-11 01:42:04 +03:00
Der_Floh dfc486fc6b Skylanders Portal IPC Server 2026-10-10 19:58:42 +00:00
izcarti 6a900d00e6 PPU Interpreter: Fix VSCR element in MFVSCR and MTVSCR
VSCR is the last big-endian word, stored in _u32[0], as the LLVM recompiler uses.
2026-10-10 22:32:11 +03:00
izcarti 31bd702e39 PPU: Add accurate vector estimates option
vrefp and vrsqrtefp are hardware exact only when enabled.
vexptefp and vlogefp are now exact by default.
2026-10-10 22:06:19 +03:00
izcarti 63ad856afa PPU LLVM: Estimate a whole vector per call 2026-10-10 22:06:19 +03:00
izcarti cb450d7e8f PPU: Hardware exact VMX estimates
vrefp, vrsqrtefp, vexptefp and vlogefp return the Cell's estimates instead
of exact results, matched bit for bit against a real PS3 with VSCR[NJ] set
and clear.
2026-10-10 22:06:19 +03:00
Max Gautier 68e0d6021c Include udev/devd rules in Linux/FreeBSD install
This allows RPCS3 to communicate with Dualshock 3,4 and DualSense
controller out of the box on Linux distributions and FreeBSD, without
the need for manual actions.
2026-10-10 19:56:54 +03:00
digant73andClaude Opus 5.5 10adf2ec39 rsx: Split the vertex interpreter source into two string literals
The single raw string literal exceeded the MSVC string literal size limit
(error C2026: string too big, trailing characters truncated).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 18:41:28 +03:00
Megamouse 987129bc91 Fix some warnings 2026-10-10 12:32:17 +02:00
Megamouse b195c2ff1b cellMusicDecode: fix deadlock and make members private 2026-10-10 12:32:17 +02:00
Malcolm f9b8c209a3 BufferUtils: Address reviews
-drop iota16, remove avx2 check, better reduction
2026-10-10 12:40:20 +03:00
Malcolm cd4d4e02ec BufferUtils: Optimize restart removal with SVE and SVE2.2 2026-10-10 12:40:20 +03:00
Malcolm d21c0d808d BufferUtils: Add table-based NEON and AVX2 compaction 2026-10-10 12:40:20 +03:00
Malcolm 52343fa2f3 BufferUtils: Add native NEON upload paths 2026-10-10 12:40:20 +03:00
Antonino Di Guardo ff91404b7c Qt: Fix race condition in qt_video_source_wrapper (#19774) 2026-10-10 08:15:18 +00:00
kd-11 e38d27157e rsx/vk: Implement rendering using no attachments 2026-10-10 10:30:35 +03:00
kd-11 0be621eb62 vk: Add support for attachment-less renderpasses 2026-10-10 10:30:35 +03:00
kd-11 78f27eaad6 rsx: Skip framebufferless draws if we have no side-effects 2026-10-10 10:30:35 +03:00
kd-11 66ba732829 rsx/gl: Implement support for framebuffer-less rendering 2026-10-10 10:30:35 +03:00
Liam 8f2fe1a99c rsx: Emulate float depth range on hosts without extended depth range support 2026-10-09 15:43:15 +00:00
kd-11 0be6d12140 vk: Remove outdated warning message about D16F being unsupported 2026-10-09 12:38:31 +03:00
kd-11 d6295528d7 rsx: Ensure F32->D24 conversions correctly round to nearest to match real hardware 2026-10-09 12:38:31 +03:00
kd-11 510f4c243e rsx: Fix depth_bounds range computation 2026-10-09 12:38:31 +03:00
kd-11 ffa4c71088 rsx: Fix DEPTH_FLOAT decoding for clear values 2026-10-09 12:38:31 +03:00
kd-11 5e89a0061e rsx: Update D16F pack/unpack logic to match hardware tests
- The format is insane. Bias is 16, not 7
- This means values outside [0,1] are impossible to encode
2026-10-09 12:38:31 +03:00
kd-11 b26e0cfa91 vk: Pause the driver manager routine when capturing media
- These are out-of-band events and should not be treated as 'real' sync points.
2026-10-09 12:38:31 +03:00
kd-11 33b2958da5 gl: Fixes for depth format upload and download 2026-10-09 12:38:31 +03:00
kd-11 cfd03fc026 vk: Fix edge cases in DEPTH format transfer logic
- Lots of edge cases and wrong sizes used for barriers
- Incorrect e4m12 conversion logic caused readback errors
2026-10-09 12:38:31 +03:00
kd-11 7c572eb4dd gl/vk: Correctly halve the invocation count when shrinking F32 to F16 2026-10-09 12:38:31 +03:00
kd-11 4d4c07c18a vk: Ensure input attachments are in uncompressed layouts 2026-10-09 12:38:31 +03:00
digant73 4c7d08582a overlays: Fix missing symbol glyphs (e.g. ★) in native UI on Windows 2026-10-09 07:52:44 +02:00
kd-11 304d544bc1 ci: Skip cache load step if PR is marked with 'CI: Disable Build Caches' label 2026-10-08 23:07:27 +03:00
kd-11 e4694a7a2c rsx/vp: Adjust constant access behavior to match real hardware
- Real hardware behavior for constants table access is complicated.
1. If you try to encode access of c[N] where N > 467 you read back 0 in the shader
2. If you try to bypass the limitation using ARL then the index wraps around 512.
3. Using the ARL bypass between 468 and 512 returns garbage.
2026-10-08 23:07:27 +03:00
kd-11 7f476dad89 rsx: Rework transform constant update logic
- This reverts commit 193975556d.
- Out of bounds writes are treated as NOP, same as real hardware
2026-10-08 23:07:27 +03:00
kd-11 222754bfd3 rsx: Use framebuffer aliasing bias to break ties between same-tag color and depth overlaps 2026-10-08 10:15:36 +03:00
Acts1631 d064a83d11 Keep title IDs within host path components
Crafted title IDs could escape serial-derived directories and redirect
recursive cache cleanup outside RPCS3's cache root. Reject structural
path components, escape identifiers used as host names, and make
cleanup matching literal and exact.
2026-10-08 08:02:13 +03:00
digant73 8ffc512beb Remove duplicate in emucore.vcxproj.filters 2026-10-07 20:25:47 +02:00
Antonino Di GuardoandClaude Opus 5.5 2bf8677d3c cellSearch: Put the whole search into the music selection context (#19750)
Analysis and fixes made by Opus 5.5.

## Bug
`cellSearchGetMusicSelectionContext` only put the selected (or a random)
track into the context, even when the search contained many tracks.
Games that pick custom music through cellSearch got a one-track
playlist, so with repeat ALL the same song played on loop forever.

## Fix
- For a search of single tracks the playlist now contains every track of
the search, and `first_track` / `current_track` point to the selected
one (random when shuffled, otherwise the first).
- `music_selection_context::find_content_id` starts from the selected
track instead of the first entry of the playlist.
- `audio_decoder` keeps the selected track first when shuffling and only
shuffles the rest.

Tested with NHL 14.

Fixes #14738
Part of #18779, #11596

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 17:41:28 +00:00
Yahfz ab412d5ddb rsx: Fix shader compilation (FP16 round_to_8bit) at low setting 2026-10-07 18:43:26 +03:00
Acts1631 e97200a884 Reject invalid RPCN message types
RPCN forwards MessageDetails from other users. The protobuf uint16
wrappers can carry 32-bit values, and narrowing them on the recipient
aborts RPCS3.

Validate both values before casting so malformed messages are
discarded.
2026-10-07 17:30:37 +03:00
Acts1631 cb63cc82ca Validate RPCN room message fields
RPCN room peers control message payloads and destination lists. Values
above the Matching2 limits overflow the recipient's fixed event buffer
and terminate the NP handler.

Reject malformed room message notifications before allocating event
data.
2026-10-07 16:17:15 +03:00
Acts1631 5f8dd1de61 Reject invalid signaling RTT timestamps
Signaling peers control the timestamps used for RTT calculation. A
future timestamp wraps the unsigned delta, and the fatal checked cast
terminates the signaling thread.

Reject future and oversized RTT values before updating peer state.
2026-10-07 15:19:45 +03:00
Megamouse 479ca74cad Update 7zip to 26.04 2026-10-07 08:20:07 +02:00
digant73andClaude Opus 5.5 925fbb2607 media_utils: Flush the audio decoder and resampler at the end of a track
The resampler buffers part of its input whenever the file's sample rate differs from the 48 kHz output,
because the output buffer was only sized for the frame's own samples. These samples piled up and were
never flushed, so a 44.1 kHz track came out 8.125% short (5-20 s cut from the end of every song).
The decoder itself was never drained either.

- Size the output for the pending resampler samples too and only append what swr_convert actually wrote.
- Flush the decoder and the resampler once the last packet has been read.
- Reserve the whole track up front, reuse the resampler output buffer and byteswap in place before
  appending, instead of growing the track buffer and allocating a buffer for every frame.

Fixes #18930 (custom music cut in Gran Turismo 5).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 07:31:29 +02:00
Megamouse 55a3aff337 gl: fix screenshots and recordings 2026-10-06 20:53:00 +02:00
brian218 12178de458 QT: Made HDD Model Name & HDD Serial Number settings accessible via GUI 2026-10-06 19:00:35 +02:00
Antonino Di Guardo 8e4659a6cb LV2: Fix sys_ppu_thread_start gate freezing PPU threads (#19707) 2026-10-06 15:59:20 +00:00
Antonino Di Guardo 532b6520b4 Add IRD based integrity check (#19421) 2026-10-06 10:22:13 +00:00
Whatcookie 1b90d1c149 rsx: Optimize recursion in the vertex program analyzer's instruction walk
Avoid use of std::function for recursion, and ensure that std::stack is backed by a vector, instead of the default deque.
2026-10-06 10:00:59 +00:00
brian218 c7642cb875 USIO: Eliminated frequent buffer memory reallocation to optimize memory performance 2026-10-06 12:31:13 +03:00
Elad 4dfdda9789 sys_net/Win32: Fix ECONNRESET on UDP socket 2026-10-06 11:59:40 +03:00
Elad 191932b854 Fix RSX capture memory allocations 2026-10-06 11:59:40 +03:00
Elad b1e8e74619 vm: Fix stack pages 2026-10-06 10:56:17 +03:00
Malcolm 183597deea RSX: use cache-line waits for event queue draining 2026-10-06 09:03:01 +02:00
brian218 2ff18d0fae QT: Added USIO Emulated Mode options to Settings GUI 2026-10-06 08:12:31 +02:00
Elad 3e7477107a Update contributors 2026-10-05 21:46:59 +03:00
Elad 7877ebe43d Fix RSX Debugger 2026-10-05 21:46:59 +03:00
Megamouse 30ee6cc358 Add brian218 to contributors 2026-10-05 20:07:01 +03:00
Megamouse 3db7c53c67 vm: fix fill64 2026-10-05 20:07:01 +03:00
Megamouse 70c8683ae2 usio: add proper mapping 2026-10-05 18:02:29 +02:00
brian218 b240813958 USIO: Made gun handler config player-specific in usio_config 2026-10-05 18:02:29 +02:00
brian218 c9fe397116 USIO & Pad: Added the ability to retrieve angle from analog sticks 2026-10-05 18:02:29 +02:00
brian218 2cc650538d USIO: Added support for analog stick input for crosshair 2026-10-05 18:02:29 +02:00
Megamouse b444d6ad24 qt: add value labels to sliders in pad settings 2026-10-05 17:08:02 +02:00
schm1dtmac ba4a4b561e [Mac OS X CI] Fixup for #19684, missed a path ugh 2026-10-05 11:16:30 +02:00
Antonino Di Guardo 7c3d7c7a23 Fix check on game data path (cellGameData API) 2026-10-05 08:52:32 +00:00
yahfz 52de29af04 vk: Don't use rebar for the vertex layout ring 2026-10-05 08:58:41 +03:00
yahfz 43a42d1a86 rsx: Fix FIFO reads while waiting for PUT 2026-10-05 01:44:55 +03:00
Elad 7b99edcfd6 rsx/savestates: Fix FIFO_control::restore_state() 2026-10-05 01:10:48 +03:00
Elad 7b7983ca1a vm/savestates: Rewrite and fix stack_guarded 2026-10-05 01:10:48 +03:00
digant73 a72c4c319b sys_fs: Add an option to emulate the BD-ROM read speed 2026-10-04 22:44:28 +03:00
Antonino Di Guardo fcbed0bc6d cellVdec: Fix decoding of streams without a leading sequence header and of reused AU buffers- #19658 2026-10-04 17:45:05 +00:00
kd-11 32b54f1864 rsx/vp: Process TXL input for potential ATTR references 2026-10-04 20:05:58 +03:00
Ani 193975556d Revert "rsx: Add bounds checking to transform constant loading"
This reverts commit 373d0ea7f0.
2026-10-04 18:16:56 +02:00
IdanandClaude Sonnet 5 d9608bf0f0 settings_dialog: fix Ctrl+Tab only cycling tabs once
Qt's QTabWidget only intercepts Ctrl+Tab/Ctrl+Shift+Tab while the tab
widget itself has keyboard focus. The dialog's own currentChanged
handler moves focus to the Close button after every tab switch, and
clicking into a tab's content moves focus to a child widget, so the
shortcut stopped working after the first switch.

Add explicit window-scoped shortcuts that cycle the tab index
directly, so Ctrl+Tab/Ctrl+Shift+Tab keep working regardless of which
widget currently has focus.

Fixes #19425

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-10-04 18:19:03 +03:00
Megamouse 46aee28f85 Remove log message and update comment 2026-10-04 08:54:12 +02:00
Elad 998298f9a8 Fix cellGemSetYaw's function unique signature (#19696)
This is the only known function that passes arguments in vector
registers.
Although we had an implementation for it, it was never used before.
Hopefully it works.
Attempting to fix "invalid" argument passed to cellGemSetYaw due to
wrong register decoding.
2026-10-04 08:20:42 +02:00
Megamouse c673da45f8 cellGem: Implement cellGemSetYaw (well, at least make it work for calibration) 2026-10-03 22:36:20 +02:00
Megamouse 1ce71092c8 Qt: Fix clearing mappings with rightclick on buttons that live in a groupbox with event filters 2026-10-03 21:55:44 +02:00
Megamouse 0d866154aa ps move: only try to reconnect once per second 2026-10-03 19:35:09 +02:00
Megamouse 0374854c80 Apply FusionBias 2026-10-03 19:35:09 +02:00
Megamouse f5edadb2ef ps move: add drift correction 2026-10-03 19:35:09 +02:00
Megamouse 103d41944c ps move: Only update ahrs orientation if a new input report arrived and handle ZCM1 half frames 2026-10-03 19:35:09 +02:00
Megamouse a664f405bc ps move: fix orientation mapping 2026-10-03 19:35:09 +02:00
Megamouse 5da9f4ab32 ps move: improve sample period 2026-10-03 19:35:09 +02:00
kd-11 db7d845e09 rsx: Implement optimized variants of iota16 and iota32
- Upto 20x faster than std::iota on msvc
- 1-3x bump for GCC and clang
2026-10-03 18:27:05 +03:00
yahfz e68ae2d051 RSX: Use 32-bit generated indices 2026-10-03 18:27:05 +03:00
Megamouse 1814fefb95 Update SDL to 3.4.18 2026-10-03 16:47:46 +02:00
Megamouse a1dc53fdf2 cellGem/Mouse: use float for x_pos and y_pos 2026-10-03 15:20:15 +02:00
Megamouse 0ca1c58e75 ps move: fix zcm2 calibration dump logging 2026-10-03 12:44:40 +02:00
Megamouse 8e245a6e15 ps move: ignore wrong input reports 2026-10-03 12:44:40 +02:00
Megamouse df7964560b ps move: make sure to read all calibration blocks 2026-10-03 12:44:40 +02:00
Megamouse 7d69855fbb ps move: fix accelerator typos in calibration 2026-10-03 12:44:40 +02:00
Megamouse 2f5c31155a ps move: mark garbage calibration as invalid and use default values 2026-10-03 12:44:40 +02:00
Megamouse 40da906c91 ps move: Ignore invalid ahrs data 2026-10-03 12:44:40 +02:00
Megamouse 5298e3b7a1 ps move: always apply calibration. we skipped that part if orientation was disabled (although you shouldn't disable it in the first place) 2026-10-03 12:44:40 +02:00
brian218 e8b4646145 USIO: Minor fixups for mouse input 2026-10-03 10:31:16 +02:00
schm1dtmac d5213de6dd [Mac OS X CI] Fix translations, Qt 6.12 changed the path 2026-10-02 23:23:02 +02:00
Yahfzandkd-11 b1f51cfaa6 rsx: Check Z-buffer dimensions when comparing framebuffer layouts
Co-authored-by: kd-11 <[email protected]>
2026-10-02 18:47:50 +00:00
kd-11 b7f6f4e80f rsx/fifo: Revert change in strict FIFO behavior when using atomic puller 2026-10-02 21:28:25 +03:00
kd-11 1c757181a0 rsx/lv2: Make the hw cursor resolution-aware 2026-10-02 19:56:35 +03:00
kd-11 5d8efbfeb9 rsx/lv2: Implement hardware bitmap cursor 2026-10-02 19:56:35 +03:00
kd-11 dd12be585e rsx/overlays: Use std::span in place of std::vector to allow arbitrary memory reads 2026-10-02 19:56:35 +03:00
kd-11 fc746de37d rsx/lv2: Document cellGcmCursor callbacks 2026-10-02 19:56:35 +03:00
yahfz f2b7edff27 acverdict 2026-10-02 18:42:30 +03:00
kd-11 18f18b305f gl/vk: Accept single-index draws for POINT rendering 2026-10-02 13:33:48 +03:00
kd-11 d10658b818 gl: Fix 2-sided stencil configuration caching 2026-10-02 13:33:48 +03:00
kd-11 b87a56979e vk/gl: Allow cached resources with upto 512 depth slices 2026-10-02 13:33:48 +03:00
kd-11 5df3970a75 vk: Fix leaky ref-counts for reused query pools (unused quirk) 2026-10-02 13:33:48 +03:00
kd-11 a3409242ce gl: Fix texture layout for RGB5A1 and BGR5A1 surfaces for readback/upload 2026-10-02 13:33:48 +03:00
kd-11 54ab16913e vk: Fix WAW barrier when clearing the WSI present surface 2026-10-02 13:33:48 +03:00
kd-11 2ccc4983fa gl: Fix partial clear of depth-only image (z16) when the DSV is dirty 2026-10-02 13:33:48 +03:00
kd-11 5741d891bb vk: Only request UAB-compatible set layouts if the bindings require it 2026-10-02 13:33:48 +03:00
kd-11 4dce4ecbe5 gl: Use proper custom slot for polygon offset state caching 2026-10-02 13:33:48 +03:00
kd-11 a7e274234f rsx/mtrsx: Fix fault range of emulated index expansion 2026-10-02 13:33:48 +03:00
kd-11 f1574d7060 rsx/nv3089: Clamp input height to 1 row
- Prevent underflow when calculating addresses. which can discard valid blit requests
2026-10-02 13:33:48 +03:00
kd-11 08b226b8f8 rsx/zcull: Fix find_query behavior
- Use umax instead of 0 to mean not-found.
2026-10-02 13:33:48 +03:00
kd-11 373d0ea7f0 rsx: Add bounds checking to transform constant loading 2026-10-02 13:33:48 +03:00
kd-11 babeda0ed2 rsx: Fix typo when replaying a constant_load_modifier_barrier 2026-10-02 13:33:48 +03:00
kd-11 c3f3529b98 rsx: Reset color write mask when re-evaluating color masks 2026-10-02 13:33:48 +03:00
brian218 96ccd89cd6 USIO: Added mouse and key/pad input support for shooter games 2026-10-02 11:13:40 +02:00
Megamouse 0850e0ff5c Update Qt to 6.12.0 2026-10-02 10:02:49 +02:00
Elad a7cbd46749 rsx: Debug commit 2026-10-02 04:56:21 +03:00
Elad 0e0cdc7c29 rsx: Make the initial GET update seq-cst 2026-10-02 04:56:21 +03:00
Elad acb249f057 rsx: Fix GET updates in NOP cmd 2026-10-02 04:56:21 +03:00
Elad a3dc7e6af4 rsx: Fix FIFO GET updates (Atomic FIFO exclusive fix) 2026-10-02 04:56:21 +03:00
Elad 982337435a rsx: Fix FIFO GET ordering after NV4097/NV406E-REF labels release 2026-10-02 04:56:21 +03:00
Elad cdc04e0d59 Fix _cellGcmInitBody 2026-10-02 04:56:21 +03:00
Elad 7aa2422a07 Fix RSX Debugger 2026-10-02 04:56:21 +03:00
Megamouse 17320679e3 VS: disable ZSTD in curl 2026-10-01 22:35:46 +02:00
Megamouse 1dd70e036e cellGem: fix cellGemHSVtoRGB 2026-10-01 22:35:46 +02:00
Megamouse 53d9522073 cellGem: Fix handle offset orientation 2026-10-01 21:18:49 +02:00
Megamouse 3b3c7f4f07 Fix infinite recursion when Emu.GetDir is /app_home/ 2026-10-01 21:00:18 +03:00
Megamouse a5515d2371 Update FAudio to 26.10 2026-10-01 18:28:25 +02:00
Antonino Di Guardo dfc0542a9f savedata: scoped filesystem sync 2026-10-01 13:38:26 +00:00
Vishrut Sachan 4d88114c92 ci: Auto-label PRs from title and linked issues 2026-10-01 15:09:35 +03:00
RipleyTom 81cbf7ba3d fix: various bugfixes
- sys_net: align sendmsg behaviour to sendto's
- np: ensure TUS info size fits into buffer
- signaling: retire all packets on timeout
2026-10-01 14:42:27 +03:00
Megamouse 83b1e07299 Fix some log messages 2026-10-01 12:42:01 +02:00
Megamouse 8307c54cd4 cellCamera: handle nullptr in cellCameraReadEx 2026-10-01 12:42:01 +02:00
Megamouse 805c931d49 cellGem: fix racing wheel mapping 2026-10-01 12:42:01 +02:00
Megamouse be69c054ee cellVoice: do not allow more than CELLVOICE_MAX_PORT ports 2026-10-01 12:25:46 +03:00
Megamouse d98d378704 cellAudioInGetDeviceInfo: fix error value 2026-10-01 12:25:46 +03:00
Megamouse d9a59820ba cellSearch: fix tag checks 2026-10-01 12:25:46 +03:00
Megamouse a1c6b12311 cellSearch: fix video content memcpy 2026-10-01 12:25:46 +03:00
Megamouse a3232fddee Update VulkanMemoryAllocator to 3.4.0 2026-10-01 10:52:00 +02:00
Megamouse 71d1a4527d _sys_ppu_thread_create: fix memory leak on error 2026-10-01 10:49:10 +03:00
Megamouse 5b350ff3e1 RawSpu: only work mmio stats when spu_log is trace 2026-10-01 09:12:56 +03:00
Megamouse 1c460fdd7f input: Fix SDL reconnect: Fix SDL open/close reference count and only try once per second 2026-10-01 07:43:05 +02:00
248 changed files with 8494 additions and 1662 deletions
+6 -6
View File
@@ -23,12 +23,12 @@ rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \
../../.ci/optimize-mac.sh rpcs3.app
# Download translations
mkdir -p "rpcs3.app/Contents/translations"
mkdir -p "rpcs3.app/Contents/Resources/translations"
ZIP_URL="https://github.com/RPCS3/rpcs3_translations/releases/latest/download/RPCS3-languages.zip"
echo "Downloading translations from: $ZIP_URL"
if curl -fsSL --retry 3 --retry-delay 60 "$ZIP_URL" -o "translations.zip"; then
echo "Successfully downloaded translations."
if unzip -o translations.zip -d "rpcs3.app/Contents/translations" >/dev/null 2>&1; then
if unzip -o translations.zip -d "rpcs3.app/Contents/Resources/translations" >/dev/null 2>&1; then
rm -f translations.zip
else
echo "Failed to extract translations.zip. Continuing without translations."
@@ -40,10 +40,10 @@ fi
# Copy Qt translations manually
QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations"
cp $QT_TRANS/qt_*.qm rpcs3.app/Contents/translations
cp $QT_TRANS/qtbase_*.qm rpcs3.app/Contents/translations
cp $QT_TRANS/qtmultimedia_*.qm rpcs3.app/Contents/translations
rm -f rpcs3.app/Contents/translations/qt_help_*.qm || true
cp $QT_TRANS/qt_*.qm rpcs3.app/Contents/Resources/translations
cp $QT_TRANS/qtbase_*.qm rpcs3.app/Contents/Resources/translations
cp $QT_TRANS/qtmultimedia_*.qm rpcs3.app/Contents/Resources/translations
rm -f rpcs3.app/Contents/Resources/translations/qt_help_*.qm || true
# Need to do this rename hack due to case insensitive filesystem
mv rpcs3.app RPCS3_.app
+51
View File
@@ -0,0 +1,51 @@
name: Label PRs
on:
pull_request_target:
types:
- opened
- edited
jobs:
Label_PRs:
name: Label from title and linked issues
runs-on: ubuntu-latest
if: github.repository == 'RPCS3/rpcs3'
permissions:
issues: read
pull-requests: write
steps:
- name: Add labels from title and linked issues
uses: actions/github-script@v9
with:
retries: 3
script: |
const AREAS = [
'Build and CI', 'CLI', 'Filesystem', 'GUI', 'Input', 'ISO', 'Localization', 'Overlay', 'Render: Vulkan', 'RSX', 'Savestates',
];
const {repository: {pullRequest: pull}} = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
labels(first: 100) { nodes { name } }
closingIssuesReferences(first: 50) { nodes { labels(first: 100) { nodes { name } } } }
timelineItems(itemTypes: [UNLABELED_EVENT], last: 100) { nodes { ... on UnlabeledEvent { label { name } } } }
}
}
}`, {...context.repo, number: context.issue.number});
if (pull.labels.nodes.some(x => AREAS.includes(x.name)))
return;
// Skip labels a maintainer removed
const removed = pull.timelineItems.nodes.map(x => x.label.name);
const fromTitle = /^rsx\b/i.test(context.payload.pull_request.title) ? ['RSX'] : [];
const fromIssues = pull.closingIssuesReferences.nodes.flatMap(i => i.labels.nodes.map(x => x.name));
const labels = [...new Set([...fromTitle, ...fromIssues])]
.filter(name => AREAS.includes(name) && !removed.includes(name));
if (labels.length > 0) {
console.log(`Adding labels: ${labels.join(', ')}`);
await github.rest.issues.addLabels({...context.repo, issue_number: context.issue.number, labels});
}
+36 -10
View File
@@ -29,8 +29,28 @@ jobs:
actions: write
contents: read
pull-requests: read
outputs:
skip_ccache: ${{ steps.pr_labels.outputs.skip_ccache }}
steps:
- name: Look for the 'Shaders' label on the merged PR
- name: "Look for the 'CI: Disable Build Caches' label on the PR"
id: pr_labels
if: github.event_name == 'pull_request'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
# Query the live labels so adding the label and re-running all jobs works without a new push
labels=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.labels[].name' 2>/dev/null) \
|| labels=$(jq -r '.pull_request.labels[]?.name' "$GITHUB_EVENT_PATH")
echo "Labels: ${labels:-<none>}"
skip=false
if grep -qix 'ci: disable build caches' <<< "$labels"; then
skip=true
echo "Builds will not restore ccache because of the 'CI: Disable Build Caches' label." >> "$GITHUB_STEP_SUMMARY"
fi
echo "skip_ccache=$skip" >> "$GITHUB_OUTPUT"
- name: "Look for the 'Shaders' or 'CI: Disable Build Caches' label on the merged PR"
id: labels
if: github.event_name == 'push' && github.repository == 'RPCS3/rpcs3' && github.ref_name == 'master'
continue-on-error: true
@@ -41,14 +61,14 @@ jobs:
for sha in $(jq -r '[.after, (.commits[]?.id)] | map(select(. != null)) | unique[]' "$GITHUB_EVENT_PATH"); do
labels=$(gh api "repos/${{ github.repository }}/commits/$sha/pulls" --jq '.[].labels[].name' 2>/dev/null || true)
echo "$sha: ${labels:-<no associated PR>}"
if grep -qix 'shaders' <<< "$labels"; then
if grep -qixE 'shaders|ci: disable build caches' <<< "$labels"; then
found=true
fi
done
echo "shaders=$found" >> "$GITHUB_OUTPUT"
echo "clean=$found" >> "$GITHUB_OUTPUT"
- name: Remove all code caches
if: steps.labels.outputs.shaders == 'true'
if: steps.labels.outputs.clean == 'true'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
@@ -63,7 +83,7 @@ jobs:
echo "Removing $key (id $id)"
gh api -X DELETE "repos/${{ github.repository }}/actions/caches/$id" --silent
done < code_caches.tsv
echo "Removed $(wc -l < code_caches.tsv) code cache(s) after a 'Shaders' PR merge." >> "$GITHUB_STEP_SUMMARY"
echo "Removed $(wc -l < code_caches.tsv) code cache(s) after merging a 'Shaders' or 'CI: Disable Build Caches' PR." >> "$GITHUB_STEP_SUMMARY"
Linux_Build:
# Only run push event on master branch of main repo, but run all PRs
@@ -112,6 +132,7 @@ jobs:
fetch-depth: 0
- name: Restore build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
@@ -179,7 +200,7 @@ jobs:
runs-on: ${{ matrix.runs-on }}
env:
CCACHE_DIR: /tmp/ccache_dir
QT_VER: '6.11.2'
QT_VER: '6.12.0'
QT_VER_MAIN: '6'
LLVM_COMPILER_VER: '22'
RELEASE_MESSAGE: ../GitHubReleaseMessage.txt
@@ -198,6 +219,7 @@ jobs:
cache: 'disable'
- name: Restore Build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
@@ -263,9 +285,9 @@ jobs:
env:
COMPILER: msvc
QT_VER_MAIN: '6'
QT_VER: '6.11.2'
QT_VER: '6.12.0'
QT_VER_MSVC: 'msvc2022'
QT_DATE: '202608131017'
QT_DATE: '202609280346'
LLVM_VER: '22.1.8'
VULKAN_VER: '1.4.341.1'
VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe'
@@ -303,6 +325,7 @@ jobs:
run: .ci/get_keys-windows.sh
- name: Restore Build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
@@ -387,9 +410,9 @@ jobs:
env:
COMPILER: msvc
QT_VER_MAIN: '6'
QT_VER: '6.11.2'
QT_VER: '6.12.0'
QT_VER_MSVC: 'msvc2022'
QT_DATE: '202608131017'
QT_DATE: '202609280346'
LLVM_VER: '22.1.8'
VULKAN_VER: '1.4.341.1'
VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe'
@@ -427,6 +450,7 @@ jobs:
run: .ci/get_keys-windows.sh
- name: Restore Build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
@@ -529,6 +553,7 @@ jobs:
p7zip
- name: Restore build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
@@ -599,6 +624,7 @@ jobs:
fetch-depth: 0
- name: Restore Build Ccache
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
uses: actions/cache/restore@main
id: restore-build-ccache
with:
+1
View File
@@ -56,6 +56,7 @@
-DCURL_USE_OPENSSL=OFF ^
-DCURL_USE_LIBPSL=OFF ^
-DCURL_ZLIB=OFF ^
-DCURL_ZSTD=OFF ^
-DCURL_USE_LIBSSH2=OFF ^
-DCMAKE_C_FLAGS="/DWOLFSSL_ALT_CERT_CHAINS /DHAVE_SNI /DNO_MD4 /DWOLFSSL_USER_SETTINGS /DUSE_IPV6 /I\"$(SolutionDir)3rdparty\curl\extra\" /I\"$(SolutionDir)3rdparty\wolfssl\wolfssl\" /I\"$(SolutionDir)3rdparty\wolfssl\wolfssl\wolfssl\" /I\"$(SolutionDir)3rdparty\wolfssl\extra\win32\"" ^
-DCMAKE_C_FLAGS_RELEASE="/O2 /Ob1 /GF /Gy" ^
+64 -54
View File
@@ -56,6 +56,63 @@ namespace pine
typedef unsigned long long SOCKET;
#ifndef _WIN32
inline std::string get_socket_path(std::string_view file_name, int slot, int default_slot)
{
#ifdef __APPLE__
const char* runtime_dir = std::getenv("TMPDIR");
#else
const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
#endif
// fallback in case macOS or other OSes don't implement the XDG base
// spec
std::string path = runtime_dir ? std::string(runtime_dir) + '/' : std::string("/tmp/");
path += file_name;
if (slot != default_slot)
{
fmt::append(path, ".%d", slot);
}
return path;
}
#endif
#ifdef _WIN32
inline const char* bind_and_listen(SOCKET sock, u16 port)
{
const int exclusive = 1;
if (setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast<const char*>(&exclusive), sizeof(exclusive)) != 0)
return "setsockopt(SO_EXCLUSIVEADDRUSE) failed";
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(port);
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr) != 1)
return "inet_pton failed";
#else
inline const char* bind_and_listen(int sock, const std::string& socket_path)
{
sockaddr_un server{};
if (socket_path.size() >= sizeof(server.sun_path))
return "socket path is too long";
server.sun_family = AF_UNIX;
std::memcpy(server.sun_path, socket_path.c_str(), socket_path.size() + 1);
unlink(socket_path.c_str());
#endif
if (bind(sock, reinterpret_cast<sockaddr*>(&server), sizeof(server)) != 0)
return "bind failed";
if (listen(sock, 4096) != 0)
return "listen failed";
return nullptr;
}
template<typename Impl>
class pine_server : public Impl
{
@@ -528,7 +585,6 @@ namespace pine
{
#ifdef _WIN32
WSADATA wsa {};
struct sockaddr_in server {};
m_sock = INVALID_SOCKET;
m_msgsock = INVALID_SOCKET;
@@ -544,45 +600,9 @@ namespace pine
return;
}
// yes very good windows s/sun/sin/g sure is fine
server.sin_family = AF_INET;
// localhost only
if (!inet_pton(server.sin_family, "127.0.0.1", &server.sin_addr.s_addr))
{
fmt::throw_exception("IPC: Failed to convert localhost");
}
server.sin_port = htons(Impl::get_port());
if (bind(m_sock, reinterpret_cast<struct sockaddr*>(&server), sizeof(server)) == SOCKET_ERROR)
{
Impl::error("IPC: Error while binding to socket! Shutting down...");
return;
}
const char* failure = bind_and_listen(m_sock, static_cast<u16>(Impl::get_port()));
#else
char* runtime_dir = nullptr;
#ifdef __APPLE__
runtime_dir = std::getenv("TMPDIR");
#else
runtime_dir = std::getenv("XDG_RUNTIME_DIR");
#endif
// fallback in case macOS or other OSes don't implement the XDG base
// spec
if (runtime_dir == nullptr)
m_socket_name = "/tmp/rpcs3.sock";
else
{
m_socket_name = runtime_dir;
m_socket_name += "/rpcs3.sock";
}
const int slot = Impl::get_port();
if (slot != IPC_DEFAULT_SLOT)
{
fmt::append(m_socket_name, ".%d", slot);
}
struct sockaddr_un server {};
m_socket_name = get_socket_path("rpcs3.sock", Impl::get_port(), IPC_DEFAULT_SLOT);
m_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (m_sock < 0)
@@ -590,23 +610,13 @@ namespace pine
Impl::error("IPC: Cannot open socket! Shutting down...");
return;
}
server.sun_family = AF_UNIX;
strcpy(server.sun_path, m_socket_name.c_str());
// we unlink the socket so that when releasing this thread the socket gets
// freed even if we didn't close correctly the loop
unlink(m_socket_name.c_str());
if (bind(m_sock, std::bit_cast<struct sockaddr*>(&server), sizeof(struct sockaddr_un)))
{
Impl::error("IPC: Error while binding to socket! Shutting down...");
return;
}
const char* failure = bind_and_listen(m_sock, m_socket_name);
#endif
// maximum queue of 4096 commands before refusing, approximated to the
// nearest legal value. We do not use SOMAXCONN as windows have this idea
// that a "reasonable" value is 5, which is not.
listen(m_sock, 4096);
if (failure)
{
Impl::error("IPC: Error while setting up socket (%s)! Shutting down...", failure);
}
}
pine_server(const pine_server&) = delete;
+5 -5
View File
@@ -20,26 +20,26 @@ The following tools are required to build RPCS3 on Windows 10 or later:
with standalone **CMake** tool.
- [Python 3.6+](https://www.python.org/downloads/) (add to PATH)
- [Qt 6.11.2](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Qt 6.12.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)). Note that future SDKs may not work.
The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode.
In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs:
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.11.2\msvc2022_64\`
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.12.0\msvc2022_64\`
- or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
**NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead.
In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool):
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.11.2\msvc2022_64\`
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.12.0\msvc2022_64\`
### Linux
These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager:
- Clang 17+ or GCC 13+
- [CMake 3.28.0+](https://www.cmake.org/download/)
- [Qt 6.11.2](https://www.qt.io/download-qt-installer)
- [Qt 6.12.0](https://www.qt.io/download-qt-installer)
- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)). Note that future SDKs may not work.
- [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend)
@@ -116,7 +116,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r
##### Configuring the Qt Plugin (if used)
1) go to `Extensions->Qt VS Tools->Qt Versions`
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.11.2\msvc2022_64`, version will fill in automatically
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.12.0\msvc2022_64`, version will fill in automatically
3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**)
4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**)
+1
View File
@@ -64,6 +64,7 @@ option(USE_LTO "Use LTO for building" ON)
option(BUILD_RPCS3_TESTS "Build RPCS3 unit tests." OFF)
option(RUN_RPCS3_TESTS "Run RPCS3 unit tests. Requires BUILD_RPCS3_TESTS" OFF)
option(USE_GAMEMODE "Choose whether to enable GameMode features or not." ON)
option(BUNDLE_UDEV_RULES "Include udev rules on Linux/FreeBSD" OFF)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/buildfiles/cmake")
+19 -1
View File
@@ -1892,11 +1892,29 @@ bool fs::utime(const std::string& path, s64 atime, s64 mtime)
#endif
}
void fs::sync()
void fs::sync(const std::string& path)
{
#ifdef _WIN32
static_cast<void>(path);
fs::g_tls_error = fs::error::unknown;
#elif defined(__linux__)
// Only flush the filesystem containing the path: a global sync() also waits on every other mounted
// filesystem and may block indefinitely on a slow or stalled one (e.g. network or FUSE mounts)
const int fd = ::open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1)
{
fs::g_tls_error = to_error(errno);
return;
}
const int res = ::syncfs(fd);
const int err = errno;
::close(fd);
fs::g_tls_error = res == 0 ? fs::error::ok : to_error(err);
#else
static_cast<void>(path);
::sync();
fs::g_tls_error = fs::error::ok;
#endif
+2 -2
View File
@@ -263,8 +263,8 @@ namespace fs
// Set file access/modification time
bool utime(const std::string& path, s64 atime, s64 mtime);
// Synchronize filesystems (TODO)
void sync();
// Synchronize the filesystem containing the given path (only that filesystem where supported)
void sync(const std::string& path);
class file final
{
+1 -1
View File
@@ -612,7 +612,7 @@ void fmt_class_string<std::source_location>::format(std::string& out, u64 arg)
#ifdef _WIN32
if (DWORD error = GetLastError())
{
fmt::append(out, " (error=%s)", error, fmt::win_error_to_string(error));
fmt::append(out, " (error=%s)", fmt::win_error_to_string(error));
}
#else
if (int error = errno)
+1 -1
View File
@@ -386,7 +386,7 @@ void decode_x64_reg_op(const u8* code, x64_op_t& out_op, x64_reg_t& out_reg, usz
case 0x67: // group 4
{
sig_log.error("decode_x64_reg_op(%016llxh): address-size override prefix found", code - out_length, prefix);
sig_log.error("decode_x64_reg_op(%016llxh): address-size override prefix found", code - out_length);
out_op = X64OP_NONE;
out_reg = X64_NOT_SET;
out_size = 0;
+2 -2
View File
@@ -1190,7 +1190,7 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
if (!addr)
{
patch_log.error("Failed to allocate 0x%x bytes for code (entry=0x%x)", alloc_size, addr, out_branch);
patch_log.error("Failed to allocate 0x%x bytes for code (entry=0x%x)", alloc_size, out_branch);
continue;
}
@@ -1436,7 +1436,7 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
if (dest_path.empty())
{
patch_log.error("Failed to patch file path at '%s': destination is not mounted", original_vfs_path, dest_vfs_path);
patch_log.error("Failed to patch file path at '%s': destination is not mounted", original_vfs_path);
continue;
}
+5
View File
@@ -28,6 +28,11 @@ class shared_mutex final
public:
constexpr shared_mutex() = default;
const atomic_t<u32>& raw() const
{
return m_value;
}
bool try_lock_shared()
{
const u32 value = m_value.load();
+2 -1
View File
@@ -55,7 +55,8 @@ namespace utils
int compare_versions(const std::string& v1, const std::string& v2, bool& ok)
{
// Check if both version strings are valid
ok = std::regex_match(v1, std::regex("[0-9.]*")) && std::regex_match(v2, std::regex("[0-9.]*"));
static const std::regex comp = std::regex("[0-9.]*");
ok = std::regex_match(v1, comp) && std::regex_match(v2, comp);
if (!ok)
{
+49
View File
@@ -0,0 +1,49 @@
# DualShock 3
notify 100 {
match "system" "USB";
match "subsystem" "INTERFACE";
match "type" "ATTACH";
match "vendor" "0x054c";
match "product" "0x0268";
action "chmod 0666 /dev/$cdev";
};
# DualShock 4
notify 100 {
match "system" "USB";
match "subsystem" "INTERFACE";
match "type" "ATTACH";
match "vendor" "0x054c";
match "product" "0x05c4";
action "chmod 0666 /dev/$cdev";
};
# DualShock 4 Wireless Adapter
notify 100 {
match "system" "USB";
match "subsystem" "INTERFACE";
match "type" "ATTACH";
match "vendor" "0x054c";
match "product" "0x0ba0";
action "chmod 0666 /dev/$cdev";
};
# DualShock 4 Slim
notify 100 {
match "system" "USB";
match "subsystem" "INTERFACE";
match "type" "ATTACH";
match "vendor" "0x054c";
match "product" "0x09cc";
action "chmod 0666 /dev/$cdev";
};
# DualSense
notify 100 {
match "system" "USB";
match "subsystem" "INTERFACE";
match "type" "ATTACH";
match "vendor" "0x054c";
match "product" "0x0ce6";
action "chmod 0666 /dev/$cdev";
};
+26
View File
@@ -0,0 +1,26 @@
# DualShock 3 over USB
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0268", MODE="0666"
# DualShock 3 over Bluetooth
KERNEL=="hidraw*", KERNELS=="*054C:0268*", MODE="0666"
# DualShock 4 over USB
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", MODE="0666"
# DualShock 4 Wireless Adapter over USB
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ba0", MODE="0666"
# DualShock 4 Slim over USB
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", MODE="0666"
# DualShock 4 over Bluetooth
KERNEL=="hidraw*", KERNELS=="*054C:05C4*", MODE="0666"
# DualShock 4 Slim over Bluetooth
KERNEL=="hidraw*", KERNELS=="*054C:09CC*", MODE="0666"
# PS5 DualSense controller over USB hidraw
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", MODE="0666", TAG+="uaccess"
# PS5 DualSense controller over bluetooth hidraw
KERNEL=="hidraw*", KERNELS=="*054C:0CE6*", MODE="0666", TAG+="uaccess"
+11
View File
@@ -225,3 +225,14 @@ if(BUILD_RPCS3_TESTS)
)
endif()
endif()
# Udev rules, per platform
if (BUNDLE_UDEV_RULES)
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
install(FILES ../config/udev/udev.rules
DESTINATION ${CMAKE_INSTALL_LIBDIR}/udev/rules.d/ RENAME 99-rpcs3.rules)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
install(FILES ../config/udev/devd.conf
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/devd/ RENAME rpcs3.conf)
endif()
endif()
+6
View File
@@ -19,6 +19,7 @@ add_library(rpcs3_emu STATIC
perf_monitor.cpp
IPC_config.cpp
IPC_socket.cpp
Io/SkylanderPortalIPC_config.cpp
)
if(USE_LTO)
@@ -137,6 +138,8 @@ target_sources(rpcs3_emu PRIVATE
../Loader/PSF.cpp
../Loader/PUP.cpp
../Loader/TAR.cpp
../Loader/IRD.cpp
../Loader/IRD_validation.cpp
../Loader/ISO.cpp
../Loader/iso_cache.cpp
../Loader/content_validation.cpp
@@ -426,6 +429,7 @@ target_sources(rpcs3_emu PRIVATE
Io/GHLtar.cpp
Io/GunCon3.cpp
Io/Infinity.cpp
Io/wiimote_config.cpp
Io/interception.cpp
Io/KamenRider.cpp
Io/KeyboardHandler.cpp
@@ -443,6 +447,7 @@ target_sources(rpcs3_emu PRIVATE
Io/RB3MidiKeyboard.cpp
Io/recording_config.cpp
Io/Skylander.cpp
Io/SkylanderPortalIPC.cpp
Io/TopShotElite.cpp
Io/TopShotFearmaster.cpp
Io/Turntable.cpp
@@ -694,6 +699,7 @@ target_link_libraries(rpcs3_emu
3rdparty::vulkan
3rdparty::glew
3rdparty::libusb
3rdparty::hidapi
3rdparty::wolfssl
3rdparty::openal
3rdparty::cubeb
+2 -2
View File
@@ -1270,8 +1270,8 @@ u32 cpu_thread::get_pc() const
}
case thread_class::rsx:
{
const auto ctrl = static_cast<const rsx::thread*>(this)->ctrl;
return ctrl ? ctrl->get.load() : umax;
const auto& ctrl = static_cast<const rsx::thread*>(this)->fifo_ctrl;
return ctrl ? ctrl->get_pos() : umax;
}
default: break;
}
+151
View File
@@ -199,3 +199,154 @@ inline struct ppu_frsqrte_lut_t
}
}
} ppu_frqrte_lut;
constexpr u32 ppu_vrefp_segments[32][2] =
{
{0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340},
{0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0},
{0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a},
{0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1},
{0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b},
{0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b},
{0x124400, 0x143}, {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d},
{0x088400, 0x11a}, {0x065000, 0x11a}, {0x041c00, 0x108}, {0x020c00, 0x106},
};
constexpr u32 ppu_vrsqrtefp_segments[32][3] =
{
{0x34fd00, 0x568, 0x00}, {0x2f9700, 0x4f3, 0x26}, {0x2aa500, 0x48d, 0xc8}, {0x261800, 0x435, 0xc8},
{0x21e400, 0x3e7, 0x26}, {0x1dfe00, 0x3a2, 0x88}, {0x1a5c00, 0x365, 0xc8}, {0x16f800, 0x32e, 0x22},
{0x13ca00, 0x2fc, 0x00}, {0x10ce00, 0x2d0, 0x00}, {0x0dfe00, 0x2a8, 0x00}, {0x0b5700, 0x283, 0x26},
{0x08d400, 0x261, 0xc8}, {0x067300, 0x243, 0x26}, {0x043100, 0x226, 0x22}, {0x020b00, 0x20b, 0x26},
{0x7ff400, 0x7a4, 0x00}, {0x785200, 0x700, 0x00}, {0x715400, 0x670, 0x00}, {0x6ae400, 0x5f2, 0x88},
{0x64f200, 0x584, 0x00}, {0x5f6e00, 0x524, 0x00}, {0x5a4c00, 0x4cc, 0x00}, {0x558000, 0x47e, 0x22},
{0x510200, 0x43a, 0x88}, {0x4cca00, 0x3fa, 0x88}, {0x48d000, 0x3c2, 0x88}, {0x450e00, 0x38e, 0x22},
{0x418200, 0x35e, 0x22}, {0x3e2400, 0x332, 0x88}, {0x3af200, 0x30a, 0x88}, {0x37e800, 0x2e6, 0x22},
};
constexpr u16 ppu_vlogefp_segments[16][2] =
{
{0x0000, 12}, {0x0600, 10}, {0x0b00, 10}, {0x1000, 9},
{0x1480, 9}, {0x1900, 9}, {0x1d80, 8}, {0x2180, 8},
{0x2580, 8}, {0x297f, 7}, {0x2cff, 7}, {0x307f, 7},
{0x33fe, 6}, {0x36fe, 6}, {0x39fe, 6}, {0x3cfe, 6},
};
constexpr u16 ppu_vexptefp_segments[16][2] =
{
{0x0006, 6}, {0x0306, 6}, {0x0606, 6}, {0x0906, 6},
{0x0c07, 7}, {0x0f87, 7}, {0x1307, 7}, {0x1680, 8},
{0x1a80, 8}, {0x1e80, 8}, {0x2288, 9}, {0x2708, 9},
{0x2b88, 9}, {0x3008, 10}, {0x3508, 10}, {0x3a08, 12},
};
constexpr void ppu_vnormalize(s32& e, u32& m)
{
const u32 shift = std::countl_zero(m) - 8;
m = (m << shift) & 0x7fffff;
e = 1 - static_cast<s32>(shift);
}
constexpr u32 ppu_vrefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s;
if (e == 0 && (nj || m == 0))
return s | 0x7f800000;
if (e == 0)
ppu_vnormalize(e, m);
const auto& seg = ppu_vrefp_segments[m >> 18];
const u32 mant = seg[0] - seg[1] * ((m >> 9) & 0x1ff);
const s32 exp = 253 - e;
if (exp >= 0xff)
return s | 0x7f800000;
if (exp > 0)
return s | exp << 23 | mant;
if (nj)
return s;
const u32 full = mant | 0x800000, shift = 1 - exp, half = 1u << (shift - 1);
const u32 rest = full & ((half << 1) - 1);
return s | ((full >> shift) + (rest > half || (rest == half && (full >> shift) & 1)));
}
constexpr u32 ppu_vrsqrtefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0x7fc00000 : 0;
if (e == 0 && (nj || m == 0))
return s | 0x7f800000;
if (s)
return 0x7fc00000;
if (e == 0)
ppu_vnormalize(e, m);
const auto& seg = ppu_vrsqrtefp_segments[(e & 1) << 4 | m >> 19];
const u32 key = (m >> 9) & 0x3ff;
return (380 - e) >> 1 << 23 | (seg[0] - ((seg[1] * key) >> 2) - ((seg[2] >> (key & 7)) & 1));
}
constexpr u32 ppu_vlogefp(u32 x, bool nj)
{
const u32 s = x & 0x80000000;
s32 e = (x >> 23) & 0xff;
u32 m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0x7fc00000 : x;
if (e == 0 && (nj || m == 0))
return 0xff800000;
if (s)
return 0x7fc00000;
if (e == 0)
ppu_vnormalize(e, m);
const u32 key = m >> 12;
const auto& seg = ppu_vlogefp_segments[key >> 7];
const s32 fixed = (e - 127) * 2048 + ((seg[0] + seg[1] * (key & 0x7f)) >> 3);
return std::bit_cast<u32>(static_cast<f32>(fixed) / 2048);
}
constexpr u32 ppu_vexptefp(u32 x, bool)
{
const u32 s = x & 0x80000000, e = (x >> 23) & 0xff, m = x & 0x7fffff;
if (e == 0xff)
return m ? x | 0x400000 : s ? 0 : x;
if (e > 150)
return s ? 0 : 0x7f800000;
const s64 mag = e >= 135 ? s64{m | 0x800000} << (e - 135) : e > 111 ? (m | 0x800000) >> (135 - e) : 0;
const s64 fixed = (s ? -mag : mag) >> 4;
const s64 exp = 127 + (fixed >> 11);
if (exp >= 0xff)
return 0x7f800000;
if (exp <= 0)
return 0;
const auto& seg = ppu_vexptefp_segments[(fixed >> 7) & 0xf];
return static_cast<u32>(exp) << 23 | ((seg[0] + seg[1] * (fixed & 0x7f)) >> 3) << 12;
}
+1 -1
View File
@@ -274,7 +274,7 @@ error_code cellAudioInGetDeviceInfo(u32 deviceNumber, u32 deviceIndex, vm::ptr<C
std::lock_guard lock(av_manager.mutex);
if (deviceNumber >= av_manager.devices.size())
return CELL_AUDIO_OUT_ERROR_DEVICE_NOT_FOUND;
return CELL_AUDIO_IN_ERROR_DEVICE_NOT_FOUND;
av_manager.copy_device_info(deviceNumber, info);
+2 -2
View File
@@ -1419,7 +1419,7 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
u64 frame_number{};
u64 bytes_read{};
if (!g_camera.get_camera_frame(g_camera.info.buffer.get_ptr(), width, height, frame_number, bytes_read))
if (!g_camera.get_camera_frame(g_camera.info.buffer ? g_camera.info.buffer.get_ptr() : nullptr, width, height, frame_number, bytes_read))
{
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
}
@@ -1998,7 +1998,7 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state
{
if (!is_attached)
{
cellCamera.warning("Camera handler not attached. Sending attach event...", static_cast<int>(state));
cellCamera.warning("Camera handler not attached (state=%d). Sending attach event...", static_cast<int>(state));
is_attached = true;
is_attached_dirty = true;
}
+28 -4
View File
@@ -188,6 +188,18 @@ struct content_permission final
}
};
// Content directory of the last cellGameDataCheckCreate2() call, measured by cellGameDataGetSizeKB()
// A game may check several different directories: Rock Band 2 checks its own game data and then Rock Band's
// in order to import its songs, so the size always refers to the dirName of the check which asked for it
struct game_data_check final
{
shared_mutex mutex; // Checks may be issued from more than one thread of the game
std::string dir; // "/dev_hdd0/game/" + dirName, empty until the first check
void set_dir(std::string_view path) { std::lock_guard lock(mutex); dir = path; }
std::string get_dir() { reader_lock lock(mutex); return dir; }
};
template<>
void fmt_class_string<content_permission::check_mode>::format(std::string& out, u64 arg)
{
@@ -693,11 +705,14 @@ error_code cellGameDataGetSizeKB(ppu_thread& ppu, vm::ptr<u32> size)
return CELL_GAMEDATA_ERROR_PARAM;
}
const std::string content_dir = g_fxo->get<game_data_check>().get_dir();
lv2_obj::sleep(ppu);
const u64 start_sleep = ppu.start_time;
const std::string local_dir = vfs::get(Emu.GetDir());
// Until the first check there is no dirName to go by: the booted directory is the game data itself for an HDD game
const std::string local_dir = vfs::get(content_dir.empty() ? Emu.GetDir() : content_dir);
const auto dirsz = fs::get_dir_size(local_dir, 1024);
@@ -709,11 +724,15 @@ error_code cellGameDataGetSizeKB(ppu_thread& ppu, vm::ptr<u32> size)
{
const auto error = fs::g_tls_error;
if (fs::exists(local_dir))
if (!fs::exists(local_dir))
{
cellGame.error("cellGameDataGetSizeKB(): Unknown failure on calculating directory '%s' size (%s)", local_dir, error);
// The content directory is only created once the status callback has returned
ppu.check_state();
*size = 0;
return CELL_OK;
}
cellGame.error("cellGameDataGetSizeKB(): Unknown failure on calculating directory '%s' size (%s)", local_dir, error);
return CELL_GAMEDATA_ERROR_FAILURE;
}
@@ -1111,6 +1130,9 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
strcpy_trunc(cbGet->getParam.titleLang[i], psf::get_string(sfo, fmt::format("TITLE_%02d", i)));
}
// The status callback may ask for the size of this directory through cellGameDataGetSizeKB()
g_fxo->get<game_data_check>().set_dir(dir);
lv2_sleep(5000, &ppu);
funcStat(ppu, cbResult, cbGet, cbSet);
@@ -1327,7 +1349,9 @@ error_code cellGameDeleteGameData(vm::cptr<char> dirName)
auto remove_gd = [&]() -> error_code
{
if (Emu.GetCat() == "GD" && Emu.GetDir().substr(Emu.GetDir().find_last_of('/') + 1) == vfs::escape(name))
const std::string_view boot_dir = fmt::trim_back_sv(Emu.GetDir(), fs::delim);
if (Emu.GetCat() == "GD" && boot_dir.substr(boot_dir.find_last_of(fs::delim) + 1) == vfs::escape(name))
{
// Boot patch cannot delete its own directory
return CELL_GAME_ERROR_NOTSUPPORTED;
+4 -1
View File
@@ -3,6 +3,8 @@
#include "Emu/IdManager.h"
#include "Emu/System.h"
#include "Utilities/StrUtil.h"
#include "cellGame.h"
LOG_CHANNEL(cellGameExec);
@@ -108,7 +110,8 @@ error_code cellGameGetBootGameInfo(vm::ptr<u32> type, vm::ptr<char> dirName, vm:
if (source_type == CELL_GAME_GAMETYPE_HDD)
{
const std::string dir_name = Emu.GetDir();
const std::string_view boot_dir = fmt::trim_back_sv(Emu.GetDir(), fs::delim);
const std::string dir_name{boot_dir.substr(boot_dir.find_last_of(fs::delim) + 1)};
if (dir_name.size() >= CELL_GAME_DIRNAME_SIZE)
{
+2
View File
@@ -389,6 +389,8 @@ u32 g_defaultCommandBufferBegin, g_defaultCommandBufferFragmentCount;
// Called by cellGcmInit
error_code _cellGcmInitBody(ppu_thread& ppu, vm::pptr<CellGcmContextData> context, u32 cmdSize, u32 ioSize, u32 ioAddress)
{
ppu.state += cpu_flag::wait;
cellGcmSys.warning("_cellGcmInitBody(context=**0x%x, cmdSize=0x%x, ioSize=0x%x, ioAddress=0x%x)", context, cmdSize, ioSize, ioAddress);
auto& gcm_cfg = g_fxo->get<gcm_config>();
+91 -76
View File
@@ -323,7 +323,7 @@ public:
{
const auto& pad = ::at32(handler->GetPads(), pad_num(i));
const bool connected = pad && pad->is_connected() && !pad->is_copilot() && i < attribute.max_connect;
const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move;
const bool is_real_move = g_cfg.io.move != move_handler::real || (pad && pad->m_pad_handler == pad_handler::move);
update_connection(i, connected && is_real_move);
}
@@ -1776,10 +1776,10 @@ static bool check_gem_num(u32 gem_num)
return gem_num < CELL_GEM_MAX_NUM;
}
static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_controller&, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_controller&, f32 x_pos, f32 y_pos)
{
const s16 x = static_cast<s16>(x_pos / (x_max / static_cast<f32>(rsx::overlays::overlay::virtual_width)));
const s16 y = static_cast<s16>(y_pos / (y_max / static_cast<f32>(rsx::overlays::overlay::virtual_height)));
const s16 x = static_cast<s16>(x_pos * rsx::overlays::overlay::virtual_width);
const s16 y = static_cast<s16>(y_pos * rsx::overlays::overlay::virtual_height);
// Note: We shouldn't use sphere_rgb here. The game will set it to black in many cases.
const gem_config_data::gem_color& rgb = gem_config_data::gem_color::get_default_color(gem_num);
@@ -1788,30 +1788,24 @@ static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_contro
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + gem_num, x, y, color, 2'000'000, false);
}
static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, f32 x_pos, f32 y_pos)
{
const auto& shared_data = g_fxo->get<gem_camera_shared>();
if (x_max <= 0) x_max = shared_data.width;
if (y_max <= 0) y_max = shared_data.height;
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
if (x_pos <= 0) x_pos -= 0.1f; else if (x_pos >= 1.0f) x_pos += 0.1f;
if (y_pos <= 0) y_pos -= 0.1f; else if (y_pos >= 1.0f) y_pos += 0.1f;
// Image coordinates in pixels
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
const f32 image_y = static_cast<f32>(y_pos) / scaling_height;
const f32 image_x = x_pos * shared_data.width;
const f32 image_y = y_pos * shared_data.height;
// Centered image coordinates in pixels
const f32 centered_x = image_x - (shared_data.width / 2.f);
const f32 centered_y = (shared_data.height / 2.f) - image_y; // Image coordinates increase downwards, so we have to invert this
// Camera coordinates in mm (centered, so it's the same as world coordinates)
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
const f32 camera_x = centered_x * mmPerPixel;
const f32 camera_y = centered_y * mmPerPixel;
@@ -1827,12 +1821,12 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
if (g_cfg.io.move != move_handler::real)
{
// Let's say the sphere is not visible if the position is at the edge of the screen
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
controller.radius_valid = x_pos > 0.0f && x_pos < 1.0f && y_pos > 0.0f && y_pos < 1.0f;
}
if (g_cfg.io.show_move_cursor)
{
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
draw_overlay_cursor(gem_num, controller, x_pos, y_pos);
}
if (g_cfg.io.paint_move_spheres)
@@ -1841,24 +1835,17 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
}
}
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data)
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, f32 x_pos, f32 y_pos, ps_move_data& move_data)
{
const auto& shared_data = g_fxo->get<gem_camera_shared>();
if (x_max <= 0) x_max = shared_data.width;
if (y_max <= 0) y_max = shared_data.height;
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
if (x_pos <= 0.0f) x_pos -= 0.1f; else if (x_pos >= 1.0f) x_pos += 0.1f;
if (y_pos <= 0.0f) y_pos -= 0.1f; else if (y_pos >= 1.0f) y_pos += 0.1f;
// Image coordinates in pixels
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
const f32 image_y = static_cast<f32>(y_pos) / scaling_height;
const f32 image_x = x_pos * shared_data.width;
const f32 image_y = y_pos * shared_data.height;
// Half of the camera image
const f32 half_width = shared_data.width / 2.f;
@@ -1869,6 +1856,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
const f32 centered_y = half_height - image_y; // Image coordinates increase downwards, so we have to invert this
// Camera coordinates in mm (centered, so it's the same as world coordinates)
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
const f32 camera_x = centered_x * mmPerPixel;
const f32 camera_y = centered_y * mmPerPixel;
@@ -1907,7 +1895,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
gem_state->quat[3] = quat.w();
// Calculate handle position based on our world coordinate and the current orientation
constexpr ps_move_data::vect<3> offset_local_mm({0.f, 0.f, -45.f}); // handle is ~45 mm below sphere
constexpr ps_move_data::vect<3> offset_local_mm({0.f, 0.f, 45.f}); // handle is ~45 mm below sphere
const ps_move_data::vect<3> offset_world = ps_move_data::rotate_vector(quat, offset_local_mm);
gem_state->handle_pos[0] = gem_state->pos[0] - offset_world.x(); // Flip x offset
@@ -1935,12 +1923,12 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
if (g_cfg.io.move != move_handler::real)
{
// Let's say the sphere is not visible if the position is at the edge of the screen
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
controller.radius_valid = x_pos > 0.0f && x_pos < 1.0f && y_pos > 0.0f && y_pos < 1.0f;
}
if (g_cfg.io.show_move_cursor)
{
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
draw_overlay_cursor(gem_num, controller, x_pos, y_pos);
}
if (g_cfg.io.paint_move_spheres)
@@ -2026,13 +2014,10 @@ static void ds3_input_to_pad(const u32 gem_num, be_t<u16>& digital_buttons, be_t
}
}
constexpr u16 ds3_max_x = 255;
constexpr u16 ds3_max_y = 255;
static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>& pad, s32& x_pos, s32& y_pos)
static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>& pad, f32& x_pos, f32& y_pos)
{
x_pos = 0;
y_pos = 0;
x_pos = 0.0f;
y_pos = 0.0f;
const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num);
cfg->handle_input(pad, true, [&](const auto& value, bool& /*abort*/)
@@ -2042,8 +2027,8 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>&
switch (value.btn)
{
case gem_btn::x_axis: x_pos = value.value; break;
case gem_btn::y_axis: y_pos = value.value; break;
case gem_btn::x_axis: x_pos = value.value / 255.0f; break;
case gem_btn::y_axis: y_pos = value.value / 255.0f; break;
default: break;
}
});
@@ -2067,16 +2052,16 @@ static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& contro
return;
}
s32 ds3_pos_x, ds3_pos_y;
f32 ds3_pos_x, ds3_pos_y;
ds3_get_stick_values(gem_num, pad, ds3_pos_x, ds3_pos_y);
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, pad->move_data);
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, pad->move_data);
}
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{
pos_to_gem_image_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y);
pos_to_gem_image_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y);
}
}
@@ -2116,11 +2101,11 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co
gem_state->angaccel[i] = pad->move_data.angaccel_world[i];
}
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data);
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, pad->move_data);
}
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{
pos_to_gem_image_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max);
pos_to_gem_image_state(gem_num, controller, gem_state, info.x_pos, info.y_pos);
}
}
@@ -2166,12 +2151,12 @@ static const std::unordered_map<gem_btn, u16> ext_btn_indices =
{ gem_btn::racing_wheel_l2, 1 },
{ gem_btn::racing_wheel_r2, 2 },
{ gem_btn::racing_wheel_paddle_l, 3 },
{ gem_btn::racing_wheel_paddle_r, 4 },
{ gem_btn::racing_wheel_paddle_r, 3 },
{ gem_btn::combo_racing_wheel_throttle, 0 },
{ gem_btn::combo_racing_wheel_l2, 1 },
{ gem_btn::combo_racing_wheel_r2, 2 },
{ gem_btn::combo_racing_wheel_paddle_l, 3 },
{ gem_btn::combo_racing_wheel_paddle_r, 4 },
{ gem_btn::combo_racing_wheel_paddle_r, 3 },
};
static u32 ext_device_id(gem_ext_id id)
@@ -2282,23 +2267,23 @@ static void input_to_ext(u32 external_device_id, CellGemExtPortData& ext, std::s
{
switch (value.btn)
{
case gem_btn::combo_racing_wheel_throttle:
case gem_btn::combo_racing_wheel_l2:
case gem_btn::combo_racing_wheel_r2:
case gem_btn::racing_wheel_throttle:
case gem_btn::racing_wheel_l2:
case gem_btn::racing_wheel_r2:
ext.custom[::at32(ext_btn_indices, value.btn)] = static_cast<u8>(value.value);
break;
case gem_btn::combo_racing_wheel_paddle_l:
case gem_btn::combo_racing_wheel_paddle_r:
case gem_btn::racing_wheel_paddle_l:
case gem_btn::racing_wheel_paddle_r:
ext.custom[::at32(ext_btn_indices, value.btn)] |= ::at32(ext_btn_map, value.btn);
break;
case gem_btn::combo_racing_wheel_d_pad_up:
case gem_btn::combo_racing_wheel_d_pad_right:
case gem_btn::combo_racing_wheel_d_pad_down:
case gem_btn::combo_racing_wheel_d_pad_left:
case gem_btn::racing_wheel_d_pad_up:
case gem_btn::racing_wheel_d_pad_right:
case gem_btn::racing_wheel_d_pad_down:
case gem_btn::racing_wheel_d_pad_left:
ext.digital1 |= ::at32(ext_btn_map, value.btn);
break;
case gem_btn::combo_racing_wheel_l1:
case gem_btn::combo_racing_wheel_r1:
case gem_btn::racing_wheel_l1:
case gem_btn::racing_wheel_r1:
ext.digital2 |= ::at32(ext_btn_map, value.btn);
break;
default:
@@ -2652,11 +2637,11 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, mouse_no);
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data);
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, move_data);
}
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{
pos_to_gem_image_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max);
pos_to_gem_image_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos);
}
}
@@ -2707,25 +2692,23 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control
if (!gem_state || !is_input_allowed())
return;
int x_pos, y_pos, x_max, y_max;
f32 x_pos, y_pos;
{
gun_thread& gun = g_fxo->get<gun_thread>();
std::scoped_lock lock(gun.handler.mutex);
x_pos = gun.handler.get_axis_x(gem_no);
y_pos = gun.handler.get_axis_y(gem_no);
x_max = gun.handler.get_axis_x_max(gem_no);
y_max = gun.handler.get_axis_y_max(gem_no);
}
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, gem_no);
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data);
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, move_data);
}
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{
pos_to_gem_image_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max);
pos_to_gem_image_state(gem_no, controller, gem_state, x_pos, y_pos);
}
}
#endif
@@ -3085,7 +3068,7 @@ error_code cellGemGetAccelerometerPositionInDevice(u32 gem_num, vm::ptr<f32> pos
error_code cellGemGetAllTrackableHues(vm::ptr<u8> hues)
{
cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)");
cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)", hues);
auto& gem = g_fxo->get<gem_config>();
@@ -3593,12 +3576,19 @@ error_code cellGemHSVtoRGB(f32 h, f32 s, f32 v, vm::ptr<f32> r, vm::ptr<f32> g,
{
cellGem.warning("cellGemHSVtoRGB(h=%f, s=%f, v=%f, r=*0x%x, g=*0x%x, b=*0x%x)", h, s, v, r, g, b);
if (s < 0.0f || s > 1.0f || v < 0.0f || v > 1.0f || !r || !g || !b)
if (!r || !g || !b)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
h = std::clamp(h, 0.0f, 360.0f);
h = std::fmod(h, 360.0f);
s = std::clamp(s, 0.0f, 1.0f);
v = std::clamp(v, 0.0f, 1.0f);
if (h < 0.0)
{
h += 360.0f;
}
const f32 c = v * s;
const f32 x = c * (1.0f - fabs(fmod(h / 60.0f, 2.0f) - 1.0f));
@@ -3639,9 +3629,9 @@ error_code cellGemHSVtoRGB(f32 h, f32 s, f32 v, vm::ptr<f32> r, vm::ptr<f32> g,
b_tmp = x;
}
*r = (r_tmp + m) * 255.0f;
*g = (g_tmp + m) * 255.0f;
*b = (b_tmp + m) * 255.0f;
*r = std::clamp(r_tmp + m, 0.0f, 1.0f);
*g = std::clamp(g_tmp + m, 0.0f, 1.0f);
*b = std::clamp(b_tmp + m, 0.0f, 1.0f);
return CELL_OK;
}
@@ -4003,9 +3993,15 @@ error_code cellGemSetRumble(u32 gem_num, u8 rumble)
return CELL_OK;
}
error_code cellGemSetYaw(u32 gem_num, vm::ptr<f32> z_direction)
error_code cellGemSetYaw(u32 gem_num, v128 z_direction)
{
cellGem.todo("cellGemSetYaw(gem_num=%d, z_direction=*0x%x)", gem_num, z_direction);
// Unpack vector argument
const f32 z_direction_x = z_direction.fr[0];
const f32 z_direction_y = z_direction.fr[1];
const f32 z_direction_z = z_direction.fr[2];
const f32 z_direction_w = z_direction.fr[3];
cellGem.warning("cellGemSetYaw(gem_num=%d, z_direction_x=%f, z_direction_y=%f, z_direction_z=%f, z_direction_w=%f)", gem_num, z_direction_x, z_direction_y, z_direction_z, z_direction_w);
auto& gem = g_fxo->get<gem_config>();
@@ -4016,12 +4012,31 @@ error_code cellGemSetYaw(u32 gem_num, vm::ptr<f32> z_direction)
return CELL_GEM_ERROR_UNINITIALIZED;
}
if (!z_direction || !check_gem_num(gem_num))
if (!check_gem_num(gem_num))
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
// TODO
if (g_cfg.io.move != move_handler::real)
{
return CELL_OK;
}
std::lock_guard pad_lock(pad::g_pad_mutex);
const auto handler = pad::get_pad_thread();
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
if (pad && pad->m_pad_handler == pad_handler::move && !pad->is_copilot())
{
// z_direction is the direction of the controller's z axis (sphere -> handle) in world coordinates.
// This function is usually used when the game wants to re-orient the ps move towards the camera, e.g. during calibration or as a surrogate for it.
// So far we've seen:
// A: (0,0,1,0): The controller points straight at the camera along the camera axis. This is our default orientation.
// B: The current sphere position (CellGemState.pos) while pointing at the camera.
// This is equal to A if the controller is on the camera axis. Otherwise the correct yaw would be atan2(x, z).
// For now we simply reset the orientation (including pitch and roll) instead of only adjusting the yaw.
pad->move_data.orientation_reset_requested = true;
}
return CELL_OK;
}
+1 -1
View File
@@ -3702,7 +3702,7 @@ error_code cellHttpEndExternalCache()
error_code cellHttpInitExternalCache(vm::ptr<void> buf, u32 size)
{
cellHttp.todo("cellHttpInitExternalCache(buf=*0x%x, size=0x%x)", size);
cellHttp.todo("cellHttpInitExternalCache(buf=*0x%x, size=0x%x)", buf, size);
auto& man = g_fxo->get<http_manager>();
std::lock_guard lock(man.mtx);
+50 -67
View File
@@ -188,76 +188,70 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
auto& dec = g_fxo->get<Music_Decode>();
std::lock_guard lock(dec.mutex);
std::scoped_lock slock(dec.decoder.m_mtx);
if (dec.decoder.has_error)
if (dec.decoder.has_error())
{
return CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE;
}
if (dec.decoder.m_size == 0)
{
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "m_size == 0" };
}
std::scoped_lock data_lock(dec.decoder.data_mutex());
const u64 size = dec.decoder.size();
ensure(dec.decoder.m_size >= dec.read_pos);
const u64 size_left = dec.decoder.m_size - dec.read_pos;
if (dec.read_pos == 0)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_START;
}
else if (!dec.decoder.track_fully_decoded || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine.
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_MID;
}
else
{
if (dec.decoder.set_next_index(true) == umax)
if (size == 0)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "size == 0" };
}
ensure(size >= dec.read_pos);
const u64 size_left = size - dec.read_pos;
if (dec.read_pos == 0)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
*position = CELL_MUSIC_DECODE_POSITION_START;
}
else if (!dec.decoder.track_fully_decoded() || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine.
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
*position = CELL_MUSIC_DECODE_POSITION_MID;
}
else
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_END;
if (dec.decoder.set_next_index(true) == umax)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
}
else
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
*position = CELL_MUSIC_DECODE_POSITION_END;
}
}
}
const u64 size_to_read = std::min(reqSize, size_left);
*readSize = size_to_read;
const u64 read_size = dec.decoder.read(buf.get_ptr(), dec.read_pos, reqSize);
*readSize = read_size;
if (size_to_read == 0)
{
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "size_to_read == 0" }; // TODO: speculative
}
std::memcpy(buf.get_ptr(), &::at32(dec.decoder.data, dec.read_pos), size_to_read);
if (size_to_read < reqSize)
{
// Set the rest of the buffer to zero to prevent loud pops at the end of the stream if the game ignores the readSize.
std::memset(vm::static_ptr_cast<u8>(buf).get_ptr() + size_to_read, 0, reqSize - size_to_read);
}
dec.read_pos += size_to_read;
s64 start_time_ms = 0;
if (!dec.decoder.timestamps_ms.empty())
{
start_time_ms = dec.decoder.timestamps_ms.front().second;
while (dec.decoder.timestamps_ms.size() > 1 && dec.read_pos >= ::at32(dec.decoder.timestamps_ms, 1).first)
if (read_size == 0)
{
dec.decoder.timestamps_ms.pop_front();
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "read_size == 0" }; // TODO: speculative
}
}
*startTime = static_cast<u32>(start_time_ms); // startTime is milliseconds
if (read_size < reqSize)
{
// Set the rest of the buffer to zero to prevent loud pops at the end of the stream if the game ignores the readSize.
std::memset(vm::static_ptr_cast<u8>(buf).get_ptr() + read_size, 0, reqSize - read_size);
}
dec.read_pos += read_size;
const s64 start_time_ms = dec.decoder.get_start_time_ms(dec.read_pos);
*startTime = static_cast<u32>(start_time_ms); // startTime is milliseconds
cellMusicDecode.trace("cell_music_decode_read(read_size=%d, samples=%d, start_time_ms=%d)", read_size, read_size / sizeof(u64), start_time_ms);
}
switch (*position)
{
@@ -272,8 +266,7 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
{
dec.read_pos = 0;
dec.decoder.clear();
dec.decoder.track_fully_consumed = 1;
dec.decoder.track_fully_consumed.notify_one();
dec.decoder.wake_up();
break;
}
default:
@@ -282,8 +275,6 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
}
}
cellMusicDecode.trace("cell_music_decode_read(size_to_read=%d, samples=%d, start_time_ms=%d)", size_to_read, size_to_read / sizeof(u64), start_time_ms);
return CELL_OK;
}
@@ -375,11 +366,7 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command)
if (!dec.func)
return CELL_MUSIC_DECODE_ERROR_GENERIC;
error_code result = CELL_OK;
{
std::scoped_lock slock(dec.decoder.m_mtx);
result = dec.set_decode_command(command);
}
const error_code result = dec.set_decode_command(command);
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{
@@ -566,11 +553,7 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command)
if (!dec.func)
return CELL_MUSIC_DECODE_ERROR_GENERIC;
error_code result = CELL_OK;
{
std::scoped_lock slock(dec.decoder.m_mtx);
result = dec.set_decode_command(command);
}
const error_code result = dec.set_decode_command(command);
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{
+8 -1
View File
@@ -2189,7 +2189,14 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
// Remove old backup
fs::remove_all(old_path);
fs::sync();
fs::sync(new_path);
#ifndef _WIN32
if (fs::g_tls_error != fs::error::ok)
{
cellSaveData.warning("savedata_op(): Failed to sync filesystem of '%s' (%s)", new_path, fs::g_tls_error);
}
#endif
// Backup old savedata
if (!vfs::host::rename(dir_path, old_path, &g_mp_sys_dev_hdd0, false))
+51 -56
View File
@@ -1313,7 +1313,7 @@ error_code cellSearchStartSceneSearch(CellSearchSceneSearchType searchType, vm::
for (u32 n = 0; n < tagNum; n++)
{
if (!tags[tagNum] || !memchr(&tags[tagNum], '\0', CELL_SEARCH_TAG_LEN_MAX))
if (!tags[n] || !memchr(tags[n].get_ptr(), '\0', CELL_SEARCH_TAG_LEN_MAX))
{
return CELL_SEARCH_ERROR_TAG;
}
@@ -1403,7 +1403,7 @@ error_code cellSearchGetContentInfoByOffset(CellSearchId searchId, s32 offset, v
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.photo, sizeof(content_info->data.photo));
break;
case CELL_SEARCH_CONTENTTYPE_VIDEO:
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.video, sizeof(content_info->data.photo));
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.video, sizeof(content_info->data.video));
break;
case CELL_SEARCH_CONTENTTYPE_MUSICLIST:
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.music_list, sizeof(content_info->data.music_list));
@@ -1690,19 +1690,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
const auto& first_content = first_content_id.second;
ensure(first_content);
const auto get_random_content = [&searchObject, &first_content]() -> shared_ptr<search_content_t>
{
if (searchObject->content_ids.size() == 1)
{
return first_content;
}
std::vector<content_id_type> result;
std::sample(searchObject->content_ids.begin(), searchObject->content_ids.end(), std::back_inserter(result), 1, std::mt19937{std::random_device{}()});
ensure(result.size() == 1);
shared_ptr<search_content_t> content = ensure(result[0].second);
return content;
};
// Index of the track to start with, if the search contains single tracks
u32 first_track = umax;
if (contentId)
{
@@ -1741,8 +1730,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
}
else if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSIC)
{
context.playlist.push_back(content->second->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, context.playlist.back());
first_track = ::narrow<u32>(std::distance(searchObject->content_ids.begin(), content));
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Found track %d: Type=0x%x, Path='%s'", content_hash, first_track, +content->second->type, content->second->infoPath.contentPath);
}
else
{
@@ -1754,47 +1743,40 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
// Abort if we can't find the playlist.
return { CELL_SEARCH_ERROR_CONTENT_NOT_FOUND, "Type: CELL_SEARCH_CONTENTTYPE_MUSICLIST" };
}
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
{
// Select random track
// TODO: whole playlist
shared_ptr<search_content_t> content = get_random_content();
context.playlist.push_back(content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path='%s'", content_hash, +content->type, context.playlist.back());
}
else
{
// Select the first track by default
// TODO: whole playlist
context.playlist.push_back(first_content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path='%s'", content_hash, +first_content->type, context.playlist.back());
}
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
// Abort if we don't have the necessary info to select a playlist.
return { CELL_SEARCH_ERROR_NOT_SUPPORTED_CONTEXT, "Type: CELL_SEARCH_CONTENTTYPE_MUSICLIST" };
}
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
if (first_content->type != CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
// Select random track
// TODO: whole playlist
shared_ptr<search_content_t> content = get_random_content();
context.playlist.push_back(content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path='%s'", +content->type, context.playlist.back());
}
else
{
// Select the first track by default
// TODO: whole playlist
context.playlist.push_back(first_content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path='%s'", +first_content->type, context.playlist.back());
if (first_track == umax)
{
// Start with a random track if shuffled, otherwise with the first track
first_track = 0;
if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
{
std::mt19937 engine{std::random_device{}()};
first_track = ::narrow<u32>(std::uniform_int_distribution<usz>(0, searchObject->content_ids.size() - 1)(engine));
}
}
// The playlist contains all the tracks of the search
for (const content_id_type& cid : searchObject->content_ids)
{
context.playlist.push_back(ensure(cid.second)->infoPath.contentPath);
}
context.first_track = context.current_track = first_track;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning %d tracks, starting with track %d: Path='%s'", context.playlist.size(), first_track, ::at32(context.playlist, first_track));
}
context.content_type = first_content->type;
context.repeat_mode = repeatMode;
context.context_option = option;
// TODO: context.first_track = ?;
// Resolve hashed paths
for (std::string& track : context.playlist)
@@ -1808,7 +1790,7 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
context.create_playlist(music_selection_context::get_next_hash());
*outContext = context.get();
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string());
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %s", context.to_string());
return CELL_OK;
}
@@ -2140,27 +2122,40 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
if (!contents_id)
return CELL_MUSIC_ERROR_PARAM;
// Search for the content that matches our current selection
// Search for the content that matches our current selection, starting with the selected track
auto& content_map = g_fxo->get<content_id_map>();
shared_ptr<search_content_t> found_content;
u64 hash = 0;
for (const std::string& track : playlist)
const usz start = first_track < playlist.size() ? first_track : 0;
u64 last_hash = 0;
for (usz i = 0; i < playlist.size(); i++)
{
if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
const std::string_view track = ::at32(playlist, (start + i) % playlist.size());
// The tracks of a music list share the hash of their directory
const u64 track_hash = std::hash<std::string_view>()(content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST ? fs::get_parent_dir_view(track) : track);
if (i > 0 && track_hash == last_hash)
{
hash = std::hash<std::string>()(fs::get_parent_dir(track));
}
else
{
hash = std::hash<std::string>()(track);
continue;
}
if (auto found = content_map.map.find(hash); found != content_map.map.end())
last_hash = track_hash;
if (auto found = content_map.map.find(track_hash); found != content_map.map.end())
{
hash = track_hash;
found_content = found->second;
break;
}
if (i == 0)
{
// Look for the selected track below if no content is found
hash = track_hash;
}
}
if (found_content)
+68 -8
View File
@@ -53,6 +53,31 @@ constexpr int averror_eof = AVERROR_EOF; // workaround for old-style-cast error
LOG_CHANNEL(cellVdec);
// Checks whether the AU holds a header that tells the decoder the picture size:
// MPEG-2: sequence header; MPEG-4 Part 2: Video Object Layer header, or a short video header (H.263 picture start code) at the start of the AU
static bool au_has_sequence_header(AVCodecID codec_id, const u8* data, u32 size)
{
if (codec_id == AV_CODEC_ID_MPEG4 && size >= 3 && data[0] == 0 && data[1] == 0 && (data[2] & 0xfc) == 0x80)
{
return true;
}
for (u32 i = 0; i + 3 < size; i++)
{
if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
{
const u8 code = data[i + 3];
if (codec_id == AV_CODEC_ID_MPEG2VIDEO ? code == 0xb3 : (code & 0xf0) == 0x20)
{
return true;
}
}
}
return false;
}
template<>
void fmt_class_string<CellVdecError>::format(std::string& out, u64 arg)
{
@@ -137,6 +162,10 @@ struct vdec_cmd
: type(_type), seq_id(_seq_id), id(_id), mode(_mode), au(std::move(_au))
{
ensure(_type == vdec_cmd_type::au_decode);
// Copy the AU now: the game may refill its buffer before the decoder thread gets to this AU
const u8* data = vm::_ptr<const u8>(au.startAddr);
au_data.assign(data, data + au.size);
}
explicit vdec_cmd(vdec_cmd_type _type, u64 _seq_id, u64 _id, s32 _framerate)
@@ -151,6 +180,7 @@ struct vdec_cmd
s32 mode{};
s32 framerate{};
CellVdecAuInfo au{};
std::vector<u8> au_data;
};
struct vdec_frame
@@ -224,6 +254,8 @@ struct vdec_context final
AVRational log_time_base{}; // Used to reduce log spam
AVRational log_framerate{}; // Used to reduce log spam
u32 skipped_au_count{}; // Used to reduce log spam
bool sequence_header_found = false;
vdec_context(s32 type, u32 /*profile*/, u32 addr, u32 size, vm::ptr<CellVdecCbMsg> func, u32 arg)
: type(type)
@@ -268,6 +300,9 @@ struct vdec_context final
fmt::throw_exception("avcodec_descriptor_get() failed (type=0x%x)", type);
}
// Only these decoders are checked for a sequence header before an AU is passed to them
sequence_header_found = codec->id != AV_CODEC_ID_MPEG2VIDEO && codec->id != AV_CODEC_ID_MPEG4;
ctx = avcodec_alloc_context3(codec);
if (!ctx)
@@ -413,13 +448,12 @@ struct vdec_context final
const bool drain = cmd->type == vdec_cmd_type::drain;
const u32 au_mode = cmd->mode;
const u32 au_addr = cmd->au.startAddr;
const u32 au_size = cmd->au.size;
const u64 au_pts = u64{cmd->au.pts.upper} << 32 | cmd->au.pts.lower;
const u64 au_dts = u64{cmd->au.dts.upper} << 32 | cmd->au.dts.lower;
au_usrd = cmd->au.userData;
packet.data = vm::_ptr<u8>(au_addr);
packet.data = cmd->au_data.data();
packet.size = au_size;
packet.pts = au_pts != umax ? au_pts : s64{smin};
packet.dts = au_dts != umax ? au_dts : s64{smin};
@@ -447,10 +481,23 @@ struct vdec_context final
{
cellVdec.trace("AU decoding: handle=0x%x, seq_id=%d, cmd_id=%d, size=0x%x, pts=0x%llx, dts=0x%llx, userdata=0x%llx", handle, cmd->seq_id, cmd->id, au_size, au_pts, au_dts, au_usrd);
if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
if (!drain && !sequence_header_found && !au_has_sequence_header(codec->id, packet.data, au_size))
{
// The decoder cannot use an AU before a sequence header (e.g. a stream continued on a new decoder,
// or one that sends its sequence header after its first pictures): skip it
if (skipped_au_count++ == 0)
{
cellVdec.warning("No sequence header yet, skipping AUs until one arrives (handle=0x%x, seq_id=%d, cmd_id=%d)", handle, cmd->seq_id, cmd->id);
}
}
else if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
{
fmt::throw_exception("AU queuing error (handle=0x%x, seq_id=%d, cmd_id=%d, error=0x%x): %s", handle, cmd->seq_id, cmd->id, ret, utils::av_error_to_string(ret));
}
else
{
sequence_header_found = true;
}
while (!abort_decode && seq_id == cmd->seq_id)
{
@@ -577,6 +624,12 @@ struct vdec_context final
decoded_frames.push_back(std::move(frame));
}
if (skipped_au_count && !decoded_frames.empty())
{
cellVdec.warning("Decoder output resumed after %d skipped AUs (handle=0x%x, seq_id=%d, cmd_id=%d)", skipped_au_count, handle, cmd->seq_id, cmd->id);
skipped_au_count = 0;
}
}
if (thread_ctrl::state() != thread_state::aborting)
@@ -610,10 +663,10 @@ struct vdec_context final
thread_ctrl::wait_for(10000);
if (elapsed++ >= 500) // 5 seconds
// Report a wait once: a game may keep the pictures of a decoder it does not display for a long time
if (++elapsed == 500) // 5 seconds
{
cellVdec.error("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
elapsed = 0;
cellVdec.warning("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
}
}
@@ -622,6 +675,11 @@ struct vdec_context final
break;
}
if (elapsed >= 500)
{
cellVdec.notice("Video au decode resumed after waiting %d seconds for a consumer. (handle=0x%x, seq_id=%d, cmd_id=%d)", elapsed / 100, handle, cmd->seq_id, cmd->id);
}
{
std::lock_guard lock{mutex};
out_queue.push_back(std::move(decoded_frames.front()));
@@ -1685,7 +1743,8 @@ error_code cellVdecDecodeAu(ppu_thread& ppu, u32 handle, CellVdecDecodeMode mode
if (!vdec->au_count.try_inc(4))
{
return CELL_VDEC_ERROR_BUSY;
// The AU queue is full: BUSY asks the caller to retry, it is not an error
return not_an_error(CELL_VDEC_ERROR_BUSY);
}
const u64 seq_id = vdec->seq_id;
@@ -1732,7 +1791,8 @@ error_code cellVdecDecodeAuEx2(ppu_thread& ppu, u32 handle, CellVdecDecodeMode m
if (!vdec->au_count.try_inc(4))
{
return CELL_VDEC_ERROR_BUSY;
// The AU queue is full: BUSY asks the caller to retry, it is not an error
return not_an_error(CELL_VDEC_ERROR_BUSY);
}
CellVdecAuInfo au_info{};
+3 -1
View File
@@ -167,7 +167,7 @@ error_code cellVoiceCreatePort(vm::ptr<u32> portId, vm::cptr<CellVoicePortParam>
return CELL_VOICE_ERROR_ARGUMENT_INVALID;
}
if (manager.ports.size() > CELLVOICE_MAX_PORT)
if (manager.ports.size() >= CELLVOICE_MAX_PORT)
return CELL_VOICE_ERROR_RESOURCE_INSUFFICIENT;
// Id: bits [8,15] seem to contain a "random" value
@@ -427,6 +427,8 @@ error_code cellVoiceInitEx(vm::ptr<CellVoiceInitParam> pArg)
auto& manager = g_fxo->get<voice_manager>();
std::lock_guard lock(manager.mtx);
if (manager.is_init)
return CELL_VOICE_ERROR_LIBVOICE_INITIALIZED;
+52 -25
View File
@@ -65,6 +65,7 @@ enum class ppu_exec_bit : u64
fix_nj,
set_vnan,
fix_vnan,
set_vest,
set_fpcc,
use_dfma,
set_cr_stats,
@@ -663,8 +664,8 @@ auto MFVSCR()
u32 sat_bit = 0;
if constexpr (((Flags == set_sat) || ...))
sat_bit = !gv_testz(sat); //!!sat._u;
d._u64[0] = 0;
d._u64[1] = u64(sat_bit | (u32{nj} << 16)) << 32;
d._u64[0] = sat_bit | (u32{nj} << 16);
d._u64[1] = 0;
};
RETURN_(ppu.vr[op.vd], ppu.sat, ppu.nj);
@@ -678,7 +679,7 @@ auto MTVSCR()
static const auto exec = [](auto&& sat, auto&& nj, auto&& jm_mask, auto&& b)
{
const u32 vscr = b._u32[3];
const u32 vscr = b._u32[0];
if constexpr (((Flags == set_sat) || ...))
sat._u = vscr & 1;
if constexpr (((Flags == use_nj || Flags == fix_nj) || ...))
@@ -1325,30 +1326,34 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VEXPTEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
// for (u32 i = 0; i < 4; i++) d._f[i] = std::exp2f(b._f[i]);
d = ppu_set_vnan<Flags...>(gv_exp2_approxfs(std::move(b)));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vexptefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
auto VLOGEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<fix_vnan>();
return ppu_exec_select<Flags...>::template select<>();
static const auto exec = [](auto&& d, auto&& b)
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
{
// for (u32 i = 0; i < 4; i++) d._f[i] = std::log2f(b._f[i]);
d = ppu_set_vnan<Flags...>(gv_log2_approxfs(std::move(b)));
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vlogefp(b._u32[i], nj);
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
@@ -2173,16 +2178,26 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VREFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
return ppu_exec_select<Flags...>::template select<set_vest, use_nj, fix_nj, set_vnan, fix_vnan>();
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask, auto&& nj)
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), b), b));
if constexpr (((Flags == set_vest) || ...))
{
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vrefp(b_._u32[i], nj);
}
}
else
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), b), b));
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask, ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
@@ -2293,16 +2308,26 @@ template <u32 Build, ppu_exec_bit... Flags>
auto VRSQRTEFP()
{
if constexpr (Build == 0xf1a6)
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
return ppu_exec_select<Flags...>::template select<set_vest, use_nj, fix_nj, set_vnan, fix_vnan>();
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask, auto&& nj)
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), gv_sqrtfs(b)), b));
if constexpr (((Flags == set_vest) || ...))
{
for (u32 i = 0; i < 4; i++)
{
d._u32[i] = ppu_vrsqrtefp(b_._u32[i], nj);
}
}
else
{
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), gv_sqrtfs(b)), b));
}
};
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask, ppu.nj);
}
template <u32 Build, ppu_exec_bit... Flags>
@@ -7362,6 +7387,8 @@ ppu_interpreter_rt_base::ppu_interpreter_rt_base() noexcept
selected += fix_vnan;
if (g_cfg.core.ppu_set_fpcc)
selected += set_fpcc;
if (g_cfg.core.ppu_set_vest)
selected += set_vest;
if (g_cfg.core.use_accurate_dfma)
selected += use_dfma;
if (g_cfg.core.ppu_debug)
+1 -1
View File
@@ -2231,7 +2231,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, bool virtual_load, const std::str
}
}
if (ppc_seg != 0x0 && !ar)
if (ppc_seg != 0x0 && !ar && !virtual_load)
{
if (ppc_seg != 0x1)
{
+21 -3
View File
@@ -20,6 +20,7 @@
#include "PPUAnalyser.h"
#include "PPUModule.h"
#include "PPUDisAsm.h"
#include "Common.h"
#include "SPURecompiler.h"
#include "timers.hpp"
#include "lv2/sys_sync.h"
@@ -2547,9 +2548,10 @@ void ppu_thread::cpu_task()
{
// The caller of sys_ppu_thread_start has higher or equal priority and waits for the exchange above:
// let it leave the syscall and run its first instructions before this thread starts
// Bounded, as it may enter another syscall right away (e.g. joining this thread), and ended on suspension, which cannot be acknowledged here
if (const auto caller = idm::get_unlocked<named_thread<ppu_thread>>(caller_id))
{
while (!is_stopped())
for (const u64 wait_start = get_system_time(); !is_stopped() && cpu_flag::suspend - state && get_system_time() - wait_start < 1000;)
{
const auto caller_state = +caller->state;
@@ -2564,7 +2566,7 @@ void ppu_thread::cpu_task()
{
constexpr u64 caller_head_start_us = 10;
for (const u64 start = get_system_time(); !is_stopped() && get_system_time() - start < caller_head_start_us;)
for (const u64 start = get_system_time(); !is_stopped() && cpu_flag::suspend - state && get_system_time() - start < caller_head_start_us;)
{
std::this_thread::yield();
}
@@ -4886,6 +4888,15 @@ extern void ppu_initialize()
}
}
template <u32 (*Estimate)(u32, bool)>
static void ppu_vec_estimate(v128& v, u32 nj)
{
for (u32 i = 0; i < 4; i++)
{
v._u32[i] = Estimate(v._u32[i], nj != 0);
}
}
bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_size)
{
ppu_log.notice("Entering ppu_initialize(const ppu_module&..)");
@@ -4966,6 +4977,10 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
{ "__escape", reinterpret_cast<u64>(+ppu_escape) },
{ "__read_maybe_mmio32", reinterpret_cast<u64>(+ppu_read_mmio_aware_u32) },
{ "__write_maybe_mmio32", reinterpret_cast<u64>(+ppu_write_mmio_aware_u32) },
{ "__vrefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vrefp>) },
{ "__vrsqrtefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vrsqrtefp>) },
{ "__vexptefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vexptefp>) },
{ "__vlogefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vlogefp>) },
};
for (u64 index = 0; index < 1024; index++)
@@ -5554,6 +5569,7 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
accurate_nj_mode,
contains_symbol_resolver,
daz_and_ftz,
accurate_vest,
__bitset_enum_max
};
@@ -5584,9 +5600,11 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
settings += ppu_settings::contains_symbol_resolver; // Avoid invalidating all modules for this purpose
if (g_cfg.core.set_daz_and_ftz)
settings += ppu_settings::daz_and_ftz;
if (g_cfg.core.ppu_set_vest)
settings += ppu_settings::accurate_vest;
// Write version, hash, CPU, settings
fmt::append(obj_name, "v8-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
fmt::append(obj_name, "v9-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
}
if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped())
+24 -4
View File
@@ -489,6 +489,16 @@ Value* PPUTranslator::VecHandleResult(Value* val, bool flush_denormals_manually)
return val;
}
Value* PPUTranslator::VecEstimate(StringRef name, u32 vr)
{
auto& entry = m_function->getEntryBlock();
const auto data = IRBuilder<>(&entry, entry.getFirstInsertionPt()).CreateAlloca(GetType<u32[4]>());
m_ir->CreateStore(GetVr(vr, VrType::vi32), data);
Call(GetType<void>(), name, data, ZExt(RegLoad(m_nj), GetType<u32>()));
return m_ir->CreateLoad(GetType<u32[4]>(), data);
}
Value* PPUTranslator::GetAddr(u64 _add)
{
if (m_reloc)
@@ -1293,14 +1303,12 @@ void PPUTranslator::VCTUXS(ppu_opcode_t op)
void PPUTranslator::VEXPTEFP(ppu_opcode_t op)
{
const auto b = get_vr<f32[4]>(op.vb);
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.exp2.v4f32", {b}}, true));
SetVr(op.vd, VecEstimate("__vexptefp", op.vb));
}
void PPUTranslator::VLOGEFP(ppu_opcode_t op)
{
const auto b = get_vr<f32[4]>(op.vb);
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.log2.v4f32", {b}}, true));
SetVr(op.vd, VecEstimate("__vlogefp", op.vb));
}
void PPUTranslator::VMADDFP(ppu_opcode_t op)
@@ -1772,6 +1780,12 @@ void PPUTranslator::VPKUWUS(ppu_opcode_t op)
void PPUTranslator::VREFP(ppu_opcode_t op)
{
if (g_cfg.core.ppu_set_vest)
{
SetVr(op.vd, VecEstimate("__vrefp", op.vb));
return;
}
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / get_vr<f32[4]>(op.vb)));
}
@@ -1815,6 +1829,12 @@ void PPUTranslator::VRLW(ppu_opcode_t op)
void PPUTranslator::VRSQRTEFP(ppu_opcode_t op)
{
if (g_cfg.core.ppu_set_vest)
{
SetVr(op.vd, VecEstimate("__vrsqrtefp", op.vb));
return;
}
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / callf<f32[4]>(get_intrinsic<f32[4]>(Intrinsic::sqrt), get_vr<f32[4]>(op.vb))));
}
+1
View File
@@ -117,6 +117,7 @@ public:
llvm::Value* VecHandleNan(llvm::Value* val);
llvm::Value* VecHandleDenormal(llvm::Value* val);
llvm::Value* VecHandleResult(llvm::Value* val, bool flush_denormals_manually = false);
llvm::Value* VecEstimate(llvm::StringRef name, u32 vr);
template <typename T>
auto vec_handle_result(T&& expr, bool flush_denormals_manually = false)
+54 -44
View File
@@ -40,32 +40,6 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
{
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
{
const u64 current = get_system_time();
if (current - old.mmio_time >= 1500)
{
old.mmio_time = current;
return true;
}
return false;
}
old.mmio_offset = offset;
return true;
});
std::string log_message = fmt::format("RawSPU[%u]: read_reg(0x%x, offset=0x%x)", index, addr, offset);
if (is_changed)
{
spu_log.trace("%s", log_message);
}
struct logger_end_t
{
u32 offset;
@@ -75,6 +49,8 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
~logger_end_t() noexcept
{
if (log_message.empty()) return;
const auto [old, value_changed] = stats->fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
@@ -96,7 +72,38 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
spu_log.trace("%s: value=0x%x", log_message, *value);
}
}
} logger_end{offset, &value, &mmio_stats, std::move(log_message)};
} logger_end{offset, &value, &mmio_stats, {}};
if (spu_log.trace)
{
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
{
const u64 current = get_system_time();
if (current - old.mmio_time >= 1500)
{
old.mmio_time = current;
return true;
}
return false;
}
old.mmio_offset = offset;
return true;
});
std::string log_message = fmt::format("RawSPU[%u]: read_reg(0x%x, offset=0x%x)", index, addr, offset);
if (is_changed)
{
spu_log.trace("%s", log_message);
}
logger_end.log_message = std::move(log_message);
}
switch (offset)
{
@@ -267,29 +274,32 @@ bool spu_thread::write_reg(const u32 addr, const u32 value)
{
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
if (spu_log.trace)
{
if (old.mmio_offset == offset && old.mmio_value == value)
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
const u64 current = get_system_time();
if (current - old.mmio_time >= 500)
if (old.mmio_offset == offset && old.mmio_value == value)
{
old.mmio_time = current;
return true;
const u64 current = get_system_time();
if (current - old.mmio_time >= 500)
{
old.mmio_time = current;
return true;
}
return false;
}
return false;
old.mmio_offset = offset;
old.mmio_value = value;
return true;
});
if (is_changed)
{
spu_log.trace("RawSPU[%u]: write_reg(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
}
old.mmio_offset = offset;
old.mmio_value = value;
return true;
});
if (is_changed)
{
spu_log.trace("RawSPU[%u]: write_reg(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
}
switch (offset)
+1 -1
View File
@@ -7601,7 +7601,7 @@ public:
const bool perm_or_zero_only = known_idx.Zero[6];
const bool consts_only = known_idx.One[7];
const bool consts_never_msb = known_idx.Zero[5];
const bool consts_never_allones = known_idx.One[5];
[[maybe_unused]] const bool consts_never_allones = known_idx.One[5];
const bool idx_selects_single = known_idx.extractBits(1, 4).isConstant();
const auto a = get_vr<u8[16]>(op.ra);
+27 -9
View File
@@ -54,6 +54,12 @@ struct hdd_read_state
u64 last_end_time = 0;
};
struct bdvd_read_state
{
shared_mutex mutex;
u64 busy_until = 0;
};
template<>
void fmt_class_string<lv2_file_type>::format(std::string& out, u64 arg)
{
@@ -363,7 +369,10 @@ const lv2_fs_mount_info& lv2_fs_mount_info_map::lookup(std::string_view path, bo
if (ret->mp->flags & lv2_mp_flag::reflection)
{
ret = &lookup(Emu.GetDir(), false, nullptr);
if (const std::string& dir = Emu.GetDir(); lv2_fs_object::get_path_root_and_trail(dir).first != dev_root)
{
ret = &lookup(dir, false, nullptr);
}
}
return *ret;
@@ -607,6 +616,17 @@ u64 lv2_file::schedule_read(u64 size, u64 start, u64 offset) const
return 0;
}
if (mp == &g_mp_sys_dev_bdvd)
{
// Nominal 2x BD-ROM transfer rate (72 Mbit/s), not measured on a PS3
constexpr u64 bdvd_transfer_rate = 9'000'000;
auto& bdvd = g_fxo->get<bdvd_read_state>();
std::lock_guard lock(bdvd.mutex);
const u64 end = std::max(bdvd.busy_until, start) + size * 1'000'000 / bdvd_transfer_rate;
bdvd.busy_until = end;
return end;
}
// Measured on a stock PS3 HDD
constexpr u64 transfer_rate = 63'000 * 1024;
constexpr u64 block_size = 0x10000;
@@ -913,15 +933,13 @@ void lv2_file::save(utils::serial& ar)
ar(stats);
const usz old_end = ar.pad_from_end(stats.size);
if (usz read_size = file.read_at(0, &ar.data[old_end], stats.size); read_size != stats.size)
if (usz read_size = file.read_at(0, ar.pad_from_end(stats.size), stats.size); read_size != stats.size)
{
ensure(read_size < stats.size);
sys_fs.error("Read less than expected! (new-size=0x%x)", read_size);
ar.trunc(stats.size - read_size);
stats.size = read_size;
ar.data.resize(old_end + stats.size);
write_to_ptr<fs::stat_t>(ar.data, patch_stats_pos, stats);
ar.patch_raw_data(patch_stats_pos, &stats, sizeof(stats));
}
}
@@ -1534,7 +1552,7 @@ error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr<void> buf, u64 nbytes, v
return CELL_EIO;
}
const u64 read_start = (g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1))
const u64 read_start = ((g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1)) || (g_cfg.vfs.emulate_bdvd_speed && file->mp == &g_mp_sys_dev_bdvd))
? get_guest_system_time() : umax;
const u64 read_offset = read_start != umax ? file->file.pos() : 0;
const u64 read_bytes = file->op_read(buf, nbytes);
@@ -2499,7 +2517,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
if (op == 0x8000000b && file->type != lv2_file_type::regular && arg->size)
{
sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, arg->size, file->name.data());
sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, arg->size, fd, file->name.data());
}
if (op == 0x8000000a && file->type != lv2_file_type::regular && arg->size >= 0x100000)
@@ -2545,7 +2563,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
file->file.seek(op_pos);
}
const u64 op_start = (op == 0x8000000a && g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1))
const u64 op_start = (op == 0x8000000a && ((g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1)) || (g_cfg.vfs.emulate_bdvd_speed && file->mp == &g_mp_sys_dev_bdvd)))
? get_guest_system_time() : umax;
const u64 done_size = op == 0x8000000a
? file->op_read(arg->buf, arg->size, op_pos)
+15 -5
View File
@@ -790,7 +790,7 @@ error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr<void> buf, u32
if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
{
fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", flags);
fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", s, flags);
}
s32 result = 0;
@@ -919,12 +919,12 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
{
fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", flags);
fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", s, flags);
}
s32 result{};
const auto sock = idm::check<lv2_socket>(s, [&](lv2_socket& sock)
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
{
auto netmsg = msg.get_ptr();
const auto success = sock.sendmsg(flags, *netmsg);
@@ -936,6 +936,8 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
return true;
}
auto lock = sock.lock();
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::write, [&](bs_t<lv2_socket::poll_t> events) -> bool
{
if (events & lv2_socket::poll_t::write)
@@ -950,10 +952,18 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
}
}
if (sock.so_sendtimeo && get_guest_system_time() - ppu.start_time > sock.so_sendtimeo)
{
result = -SYS_NET_EWOULDBLOCK;
lv2_obj::awake(&ppu);
return true;
}
sock.set_poll_event(lv2_socket::poll_t::write);
return false;
});
lv2_obj::prepare_for_sleep(ppu);
lv2_obj::sleep(ppu);
return false;
});
@@ -972,7 +982,7 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
{
break;
}
thread_ctrl::wait_on(ppu.state, state);
ppu.state.wait(state);
}
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
@@ -998,7 +1008,7 @@ error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr<void> buf, u32 l
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
{
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags);
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", s, flags);
}
if (addr && addrlen < sizeof(sys_net_sockaddr))
@@ -96,6 +96,7 @@ void lv2_socket_native::set_socket(socket_type native_socket, lv2_socket_family
set_default_buffers();
set_non_blocking();
set_dgram_disable_connreset();
}
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock)
@@ -1211,6 +1212,14 @@ void lv2_socket_native::set_non_blocking()
np::set_socket_non_blocking(native_socket);
}
void lv2_socket_native::set_dgram_disable_connreset()
{
if (type == SYS_NET_SOCK_DGRAM)
{
np::set_dgram_socket_disable_connreset(native_socket);
}
}
void lv2_socket_native::get_sockinfo(sys_net_sockinfo_t& info)
{
if (type != SYS_NET_SOCK_STREAM)
@@ -64,6 +64,7 @@ private:
void set_socket(socket_type native_socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol);
void set_default_buffers();
void set_non_blocking();
void set_dgram_disable_connreset();
private:
// Value keepers
@@ -53,6 +53,7 @@ nt_p2p_port::nt_p2p_port(u16 port)
fmt::throw_exception("Failed to create DGRAM socket for P2P socket: %s!", get_last_error(true));
np::set_socket_non_blocking(p2p_socket);
np::set_dgram_socket_disable_connreset(p2p_socket);
u32 optval = 131072; // value obtained from DECR for a SOCK_DGRAM_P2P socket(should maybe be bigger for actual socket?)
if (setsockopt(p2p_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&optval), sizeof(optval)) != 0)
+1
View File
@@ -526,6 +526,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::p
if (!vm::read_string(threadname.addr(), max_size, ppu_name, true))
{
vm::dealloc(stack_base);
dct.free(stack_size);
return CELL_EFAULT;
}
+2 -2
View File
@@ -365,6 +365,8 @@ std::function<void(void*)> lv2_prx::load(utils::serial& ar)
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
file = make_file_view(std::move(file), offset, umax);
prx = ppu_load_prx(ppu_prx_object{decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic))}, false, path, offset, &ar);
ensure(prx);
prx->m_loaded_flags = std::move(loaded_flags);
prx->m_external_loaded_flags = std::move(external_flags);
@@ -372,8 +374,6 @@ std::function<void(void*)> lv2_prx::load(utils::serial& ar)
{
prx->restore_exports();
}
ensure(prx);
}
else
{
+93 -3
View File
@@ -9,6 +9,8 @@
#include "Emu/Memory/vm_locking.h"
#include "Emu/RSX/Core/RSXEngLock.hpp"
#include "Emu/RSX/Core/RSXReservationLock.hpp"
#include "Emu/RSX/Overlays/overlay_cursor.h"
#include "Emu/RSX/Overlays/overlay_manager.h"
#include "Emu/RSX/RSXThread.h"
#include "util/asm.hpp"
#include "sys_event.h"
@@ -36,6 +38,68 @@ void fmt_class_string<sys_rsx_error>::format(std::string& out, u64 arg)
});
}
// ---- Hardware cursor ----
// The hardware cursor is part of the display circuitry. It is not rendered directly by PGRAPH or any of the other 2D objects.
// Emulation is implemented using the overlay system. It has a fixed 64x64 bitmap size.
rsx::overlays::bitmap_cursor* _sys_rsx_get_cursor()
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
return manager->get<rsx::overlays::bitmap_cursor>().get();
}
return nullptr;
}
void _sys_rsx_init_cursor_overlay()
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
auto cursor = manager->create<rsx::overlays::bitmap_cursor>();
if (const auto avconfig = g_fxo->try_get<rsx::avconf>())
{
cursor->set_screen_size(
::narrow<u16>(avconfig->resolution_x),
::narrow<u16>(avconfig->resolution_y));
}
}
}
void _sys_rsx_enable_cursor_overlay()
{
if (auto cursor = _sys_rsx_get_cursor())
{
cursor->enable();
}
}
void _sys_rsx_disable_cursor_overlay()
{
if (auto cursor = _sys_rsx_get_cursor())
{
cursor->disable();
}
}
void _sys_rsx_move_cursor_overlay(s32 x, s32 y)
{
if (auto cursor = _sys_rsx_get_cursor())
{
cursor->set_pos(x, y);
}
}
void _sys_rsx_update_cursor_image(u32 cursor_offset)
{
if (auto cursor = _sys_rsx_get_cursor())
{
cursor->set_bitmap(cursor_offset);
}
}
// ---- Util ----
static u64 rsx_timeStamp()
{
return get_timebased_time();
@@ -142,6 +206,8 @@ bool rsx::thread::send_event(u64 data1, u64 event_flags, u64 data3)
return true;
}
// ---- Event Queue ----
void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64 wait_timeout_ms = 1000ull)
{
const auto& driverInfo = *vm::_ptr<RsxDriverInfo>(rsxthr->driver_info);
@@ -179,8 +245,7 @@ void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64
break;
}
// Wait
thread_ctrl::wait_for(100);
utils::spin_on_cacheline_once(queue->mutex.raw(), 0u, 100);
// Check for timeout
if (wait_timeout_ms == umax)
@@ -204,6 +269,8 @@ void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64
}
}
// ---- LV2 ----
error_code sys_rsx_device_open(cpu_thread& cpu)
{
cpu.state += cpu_flag::wait;
@@ -777,7 +844,30 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64
});
break;
}
case 0x10D: // Called by cellGcmInitCursor
case 0x10b:
// when a4=3, cellGcmSetCursorPosition(a5=xpos, a6=ypos)
// when a4=2, cellGcmSetCursorImageOffset(a5=offset)
if (a4 == 3)
_sys_rsx_move_cursor_overlay(static_cast<s32>(a5), static_cast<s32>(a6));
else if (a4 == 2)
_sys_rsx_update_cursor_image(static_cast<u32>(a5));
else
sys_rsx.error("Unknown subfunction 0x%llx (package=0x10b)", a4);
break;
case 0x10c:
// when a4=1, cellGcmSetCursorEnable()
// when a4=2, cellGcmSetCursorDisable()
if (a4 == 1)
_sys_rsx_enable_cursor_overlay();
else if (a4 == 2)
_sys_rsx_disable_cursor_overlay();
else
sys_rsx.error("Unknown subfunction 0x%llx (package=0x10c)", a4);
break;
case 0x10d:
// cellGcmInitCursor(a3=1, a4=1, a5=0, a6=0)
ensure(a4 == 1);
_sys_rsx_init_cursor_overlay();
break;
case 0x300: // Tiles
+1 -1
View File
@@ -24,7 +24,7 @@ error_code sys_sm_get_params(vm::ptr<u8> a, vm::ptr<u8> b, vm::ptr<u32> c, vm::p
error_code sys_sm_get_ext_event2(vm::ptr<u64> a1, vm::ptr<u64> a2, vm::ptr<u64> a3, u64 a4)
{
sys_sm.trace("sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", a1, a2, a3, a4);
sys_sm.trace("sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", a1, a2, a3, a4, a4);
if (a4 != 0 && a4 != 1)
{
+1 -1
View File
@@ -107,7 +107,7 @@ error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, u32 num_sectors
error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, vm::ptr<void> in, u64 inlen, vm::ptr<void> out, u64 outlen)
{
sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%, inlen=0x%x, out=*0x%x, outlen=0x%x)", dev_handle, cmd, in, inlen, out, outlen);
sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%x, inlen=0x%x, out=*0x%x, outlen=0x%x)", dev_handle, cmd, in, inlen, out, outlen);
return CELL_OK;
}
+2 -7
View File
@@ -292,19 +292,14 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp
}
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
{
std::memcpy(buf, &gt, sizeof(GameTablet_data));
return;
}
static u8 noise_x = 0; // Toggle the LSB to simulate a noisy signal, Instant Artist dislikes a pen held perfectly still
static u8 noise_y = 0;
constexpr s32 tablet_max_x = 1920;
constexpr s32 tablet_max_y = 1080;
const s32 tablet_x_pos = (mouse_data.x_pos * tablet_max_x / mouse_data.x_max) ^ noise_x;
const s32 tablet_y_pos = (mouse_data.y_pos * tablet_max_y / mouse_data.y_max) ^ noise_y;
const s32 tablet_x_pos = static_cast<s32>(mouse_data.x_pos * tablet_max_x) ^ noise_x;
const s32 tablet_y_pos = static_cast<s32>(mouse_data.y_pos * tablet_max_y) ^ noise_y;
noise_x ^= 0x1;
noise_y ^= 0x1;
+107 -9
View File
@@ -3,9 +3,15 @@
#include "MouseHandler.h"
#include "Emu/IdManager.h"
#include "Emu/Io/guncon3_config.h"
#include "Emu/Io/wiimote_config.h"
#include "Emu/Cell/lv2/sys_usbd.h"
#include "Emu/system_config.h"
#include "Emu/RSX/Overlays/overlay_cursor.h"
#include "Input/wiimote_handler.h"
#include "Input/pad_thread.h"
#include <cmath>
#include <climits>
#include <algorithm>
LOG_CHANNEL(guncon3_log);
@@ -127,6 +133,18 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
: usb_device_emulated(location)
, m_controller_index(controller_index)
{
{
std::lock_guard lock(s_instances_mutex);
s_instances.push_back(this);
// Sort instances by controller index (P1 < P2 < P3...)
// This ensures that the first available GunCon (e.g. at P3) takes the first Wiimote,
// and the second (e.g. at P4) takes the second Wiimote.
std::sort(s_instances.begin(), s_instances.end(), [](auto* a, auto* b)
{
return a->m_controller_index < b->m_controller_index;
});
}
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE,
UsbDeviceDescriptor {
.bcdUSB = 0x0110,
@@ -174,6 +192,11 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
usb_device_guncon3::~usb_device_guncon3()
{
std::lock_guard lock(s_instances_mutex);
if (auto it = std::find(s_instances.begin(), s_instances.end(), this); it != s_instances.end())
{
s_instances.erase(it);
}
}
void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
@@ -188,6 +211,81 @@ void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wVa
extern bool is_input_allowed();
bool usb_device_guncon3::handle_wiimote(GunCon3_data& gc)
{
if (!get_wiimote_config().use_for_guncon.get())
return false;
auto* wm = wiimote_handler::get_instance();
auto states = wm->get_states();
// Determine which Wiimote to use based on our ordinal position among all GunCons
s64 my_wiimote_index = -1;
{
std::lock_guard lock(s_instances_mutex);
auto found = std::find(s_instances.begin(), s_instances.end(), this);
if (found != s_instances.end())
{
my_wiimote_index = std::distance(s_instances.begin(), found);
}
}
if (my_wiimote_index < 0 || static_cast<usz>(my_wiimote_index) >= states.size())
return false;
const auto& ws = states[my_wiimote_index];
if (!ws.connected)
return false;
const auto& map = get_wiimote_config().guncon_mapping;
const auto is_pressed = [&](wiimote_button btn) { return (ws.buttons & static_cast<u16>(btn)) != 0; };
if (is_pressed(map.trigger.get())) gc.btn_trigger = 1;
// Wiimote to GunCon3 Button Mapping
if (is_pressed(map.a1.get())) gc.btn_a1 = 1;
if (is_pressed(map.a2.get())) gc.btn_a2 = 1;
if (is_pressed(map.a3.get())) gc.btn_a3 = 1;
if (is_pressed(map.b1.get())) gc.btn_b1 = 1;
if (is_pressed(map.b2.get())) gc.btn_b2 = 1;
if (is_pressed(map.b3.get())) gc.btn_b3 = 1;
if (is_pressed(map.c1.get())) gc.btn_c1 = 1;
if (is_pressed(map.c2.get())) gc.btn_c2 = 1;
// Secondary / Hardcoded Alts
if (is_pressed(map.b1_alt.get())) gc.btn_b1 = 1;
if (is_pressed(map.b2_alt.get())) gc.btn_b2 = 1;
if (ws.ir[0].x < 1023)
{
// Map Wiimote IR (0..1023) to GunCon3 range (-32768..32767)
const s32 raw_x = ws.ir[0].x;
const s32 raw_y = ws.ir[0].y;
const s32 x_res = SHRT_MAX - (raw_x * USHRT_MAX / 1023);
const s32 y_res = SHRT_MAX - (raw_y * USHRT_MAX / 767);
gc.gun_x = static_cast<s16>(std::clamp(x_res, SHRT_MIN, SHRT_MAX));
gc.gun_y = static_cast<s16>(std::clamp(y_res, SHRT_MIN, SHRT_MAX));
if (g_cfg.io.show_move_cursor)
{
const s16 ax = static_cast<s16>((gc.gun_x + SHRT_MAX + 1) * rsx::overlays::overlay::virtual_width / USHRT_MAX);
const s16 ay = static_cast<s16>((SHRT_MAX - gc.gun_y) * rsx::overlays::overlay::virtual_height / USHRT_MAX);
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + my_wiimote_index, ax, ay, { 1.0f, 1.0f, 1.0f, 1.0f }, 100'000, false);
}
if (ws.ir[1].x < 1023)
{
const s32 dx = static_cast<s32>(ws.ir[0].x) - ws.ir[1].x;
const s32 dy = static_cast<s32>(ws.ir[0].y) - ws.ir[1].y;
gc.gun_z = static_cast<s16>(std::sqrt(dx * dx + dy * dy));
}
}
return true;
}
void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer)
{
transfer->fake = true;
@@ -213,6 +311,12 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
return;
}
if (handle_wiimote(gc))
{
guncon3_encode(&gc, buf, m_key.data());
return;
}
if (g_cfg.io.mouse == mouse_handler::null)
{
guncon3_log.warning("GunCon3 requires a Mouse Handler enabled");
@@ -270,7 +374,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
mouse_handler.Init(4);
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
if (mouse_index >= mouse_handler.GetMice().size())
{
guncon3_encode(&gc, buf, m_key.data());
@@ -280,15 +384,9 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
cfg->handle_input(mouse_data, input_callback);
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
{
guncon3_encode(&gc, buf, m_key.data());
return;
}
// Expand 0..+wh to -32767..+32767
gc.gun_x = (mouse_data.x_pos * USHRT_MAX / mouse_data.x_max) - SHRT_MAX;
gc.gun_y = (mouse_data.y_pos * -USHRT_MAX / mouse_data.y_max) + SHRT_MAX;
gc.gun_x = static_cast<s32>(mouse_data.x_pos * USHRT_MAX) - SHRT_MAX;
gc.gun_y = static_cast<s32>(mouse_data.y_pos * -USHRT_MAX) + SHRT_MAX;
}
guncon3_encode(&gc, buf, m_key.data());
+7
View File
@@ -1,6 +1,8 @@
#pragma once
#include "Emu/Io/usb_device.h"
#include <vector>
#include <mutex>
class usb_device_guncon3 : public usb_device_emulated
{
@@ -14,4 +16,9 @@ public:
private:
u32 m_controller_index;
std::array<u8, 8> m_key{};
bool handle_wiimote(struct GunCon3_data& gc);
static inline std::vector<usb_device_guncon3*> s_instances;
static inline std::mutex s_instances_mutex;
};
+1 -1
View File
@@ -1275,7 +1275,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
// Change device mode
u8 cmd = buf[1];
u8 arg = buf[2];
if (buf[8] == 0xf8) // we have 2 commands back to back
if (buf_size >= 11 && buf[8] == 0xf8) // we have 2 commands back to back
{
cmd = buf[9];
arg = buf[10];
+10 -10
View File
@@ -71,8 +71,8 @@ void MouseHandlerBase::Button(u32 index, u8 button, bool pressed)
MouseData new_data{};
new_data.update = CELL_MOUSE_DATA_UPDATE;
new_data.buttons = mouse.buttons;
new_data.pixel_x = mouse.x_pos;
new_data.pixel_y = mouse.y_pos;
new_data.pixel_x = mouse.x_pos_pixels;
new_data.pixel_y = mouse.y_pos_pixels;
datalist.push_back(std::move(new_data));
}
@@ -102,8 +102,8 @@ void MouseHandlerBase::Scroll(u32 index, s8 x, s8 y)
new_data.buttons = mouse.buttons;
new_data.wheel = y;
new_data.tilt = x;
new_data.pixel_x = mouse.x_pos;
new_data.pixel_y = mouse.y_pos;
new_data.pixel_x = mouse.x_pos_pixels;
new_data.pixel_y = mouse.y_pos_pixels;
datalist.push_back(std::move(new_data));
}
@@ -135,8 +135,8 @@ void MouseHandlerBase::Move(u32 index, s32 x_pos_new, s32 y_pos_new, s32 x_max,
if (!is_relative)
{
// The PS3 expects relative mouse movement, so we have to calculate it with the last absolute position.
x_delta = x_pos_new - mouse.x_pos;
y_delta = y_pos_new - mouse.y_pos;
x_delta = x_pos_new - mouse.x_pos_pixels;
y_delta = y_pos_new - mouse.y_pos_pixels;
}
new_data.x_axis = static_cast<s8>(std::clamp(x_delta, -127, 128));
@@ -144,10 +144,10 @@ void MouseHandlerBase::Move(u32 index, s32 x_pos_new, s32 y_pos_new, s32 x_max,
new_data.pixel_x = x_pos_new;
new_data.pixel_y = y_pos_new;
mouse.x_max = x_max;
mouse.y_max = y_max;
mouse.x_pos = x_pos_new;
mouse.y_pos = y_pos_new;
mouse.x_pos = x_max == 0 ? 0.0f : std::clamp(x_pos_new / static_cast<f32>(x_max), 0.0f, 1.0f);
mouse.y_pos = y_max == 0 ? 0.0f : std::clamp(y_pos_new / static_cast<f32>(y_max), 0.0f, 1.0f);
mouse.x_pos_pixels = x_pos_new;
mouse.y_pos_pixels = y_pos_new;
//CellMouseRawData& rawdata = GetRawData(p);
//rawdata.data[rawdata.len % CELL_MOUSE_MAX_CODES] = 0; // (TODO)
+7 -7
View File
@@ -112,10 +112,10 @@ using MouseDataList = std::deque<MouseData>;
struct Mouse
{
s32 x_pos = 0;
s32 y_pos = 0;
s32 x_max = 0;
s32 y_max = 0;
f32 x_pos = 0.0f;
f32 y_pos = 0.0f;
s32 x_pos_pixels = 0;
s32 y_pos_pixels = 0;
u8 buttons = 0; // actual mouse button positions
MouseTabletDataList m_tablet_datalist{};
@@ -154,9 +154,9 @@ public:
MouseInfo& GetInfo() { return m_info; }
std::vector<Mouse>& GetMice() { return m_mice; }
MouseDataList& GetDataList(const u32 mouse) { return m_mice[mouse].m_datalist; }
MouseTabletDataList& GetTabletDataList(const u32 mouse) { return m_mice[mouse].m_tablet_datalist; }
MouseRawData& GetRawData(const u32 mouse) { return m_mice[mouse].m_rawdata; }
MouseDataList& GetDataList(const usz mouse) { return ::at32(m_mice, mouse).m_datalist; }
MouseTabletDataList& GetTabletDataList(const usz mouse) { return ::at32(m_mice, mouse).m_tablet_datalist; }
MouseRawData& GetRawData(const usz mouse) { return ::at32(m_mice, mouse).m_rawdata; }
stx::init_mutex init;
+190 -50
View File
@@ -181,11 +181,22 @@ u16 PadHandlerBase::ConvertAxis(f32 value)
return static_cast<u16>((value + 1.0) * (255.0 / 2.0));
}
void PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor)
u8 PadHandlerBase::ConvertAngleToU8(f32 angle, f32 distance_to_center)
{
if (!squircle_factor)
return;
if (distance_to_center < 0.5f)
return 0;
constexpr f32 two_pi = std::numbers::pi_v<f32> * 2;
angle = -angle;
if (angle < 0)
angle += two_pi;
return static_cast<u8>(std::round(angle / two_pi * 0x100));
}
std::tuple<f32, f32> PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor)
{
constexpr f32 radius = 127.5f;
// convert inX and Y to a (-1, 1) vector;
@@ -196,13 +207,18 @@ void PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_fac
const f32 angle = std::atan2(y, x);
const f32 distance_to_center = std::min(1.0f, std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f)));
// now find len/point on the given squircle from our current angle and radius in polar coords
// https://thatsmaths.com/2016/07/14/squircles/
const f32 new_len = (1 + std::pow(std::sin(2 * angle), 2.f) / (squircle_factor / 1000.f)) * distance_to_center;
if (squircle_factor)
{
// now find len/point on the given squircle from our current angle and radius in polar coords
// https://thatsmaths.com/2016/07/14/squircles/
const f32 new_len = (1 + std::pow(std::sin(2 * angle), 2.f) / (squircle_factor / 1000.f)) * distance_to_center;
// we now have len and angle, convert to cartesian
inX = Clamp0To255(std::round(((new_len * std::cos(angle)) + 1) * radius));
inY = Clamp0To255(std::round(((new_len * std::sin(angle)) + 1) * radius));
// we now have len and angle, convert to cartesian
inX = Clamp0To255(std::round(((new_len * std::cos(angle)) + 1) * radius));
inY = Clamp0To255(std::round(((new_len * std::sin(angle)) + 1) * radius));
}
return std::tuple<f32, f32>(angle, distance_to_center);
}
void PadHandlerBase::init_configs()
@@ -422,16 +438,13 @@ void PadHandlerBase::get_motion_sensors(const std::string& pad_id, const motion_
callback(pad_id, std::move(preview_values));
}
void PadHandlerBase::convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const
void PadHandlerBase::convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling, f32& angle, f32& distance_to_center) const
{
// Normalize our stick axis based on the deadzone
std::tie(x_out, y_out) = NormalizeStickDeadzone(x_in, y_in, deadzone, anti_deadzone);
// Apply pad squircling if necessary
if (padsquircling != 0)
{
ConvertToSquirclePoint(x_out, y_out, padsquircling);
}
std::tie(angle, distance_to_center) = ConvertToSquirclePoint(x_out, y_out, padsquircling);
}
// Update the pad button values based on their type and thresholds. With this you can use axis or triggers as buttons or vice versa
@@ -774,10 +787,11 @@ void PadHandlerBase::get_mapping(const pad_ensemble& binding)
}
u16 lx, ly, rx, ry;
f32 l_angle, l_distance_to_center, r_angle, r_distance_to_center;
// Normalize and apply pad squircling
convert_stick_values(lx, ly, stick_val[0], stick_val[1], cfg->lstickdeadzone, cfg->lstick_anti_deadzone, cfg->lpadsquircling);
convert_stick_values(rx, ry, stick_val[2], stick_val[3], cfg->rstickdeadzone, cfg->rstick_anti_deadzone, cfg->rpadsquircling);
convert_stick_values(lx, ly, stick_val[0], stick_val[1], cfg->lstickdeadzone, cfg->lstick_anti_deadzone, cfg->lpadsquircling, l_angle, l_distance_to_center);
convert_stick_values(rx, ry, stick_val[2], stick_val[3], cfg->rstickdeadzone, cfg->rstick_anti_deadzone, cfg->rpadsquircling, r_angle, r_distance_to_center);
if (m_type == pad_handler::ds4)
{
@@ -799,6 +813,9 @@ void PadHandlerBase::get_mapping(const pad_ensemble& binding)
pad->m_sticks[2].m_value = rx;
pad->m_sticks[3].m_value = 255 - ry;
}
pad->m_angles[0] = ConvertAngleToU8(l_angle, l_distance_to_center);
pad->m_angles[1] = ConvertAngleToU8(r_angle, r_distance_to_center);
}
void PadHandlerBase::process()
@@ -960,6 +977,14 @@ void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
return;
}
// The game told us that the controller currently points at the camera (see cellGemSetYaw)
if (std::exchange(pad->move_data.orientation_reset_requested, false))
{
device->reset_orientation();
pad->move_data.quaternion = ps_move_data::default_quaternion;
return;
}
if (!pad->move_data.orientation_enabled || pad->get_orientation_reset_button_active())
{
// This can be called extensively in quick succession, so let's just reset the pointer instead of creating a new object.
@@ -971,16 +996,43 @@ void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
device->update_orientation(pad->move_data);
}
static void set_fusion_settings(FusionAhrs* ahrs, f32 sample_rate, bool drift_correction)
{
FusionAhrsSettings settings = fusionAhrsDefaultSettings;
settings.sampleRate = sample_rate;
settings.convention = FusionConvention::FusionConventionEnu;
if (drift_correction)
{
// Continuously pull the inclination towards the gravity measured by the accelerometer.
// The accelerometer is ignored while the device is accelerated (e.g. swung), unless that lasts longer than the rejection timeout.
settings.gain = 0.5f;
settings.accelerationRejection = 10.0f; // degrees
settings.magneticRejection = 10.0f; // degrees
settings.rejectionTimeout = 5.0f; // seconds
}
else
{
// Only use the accelerometer during the startup period. Afterwards we only integrate the gyro.
settings.gain = 0.0f;
}
FusionAhrsSetSettings(ahrs, &settings);
}
void PadDevice::reset_orientation()
{
// Initialize Fusion
ahrs = std::make_shared<FusionAhrs>();
FusionAhrsInitialise(ahrs.get());
FusionAhrsSettings settings = fusionAhrsDefaultSettings;
settings.convention = FusionConvention::FusionConventionEnu;
settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
FusionAhrsSetSettings(ahrs.get(), &settings);
ahrs_sample_rate = fusionAhrsDefaultSettings.sampleRate;
ahrs_measured_sample_rate = 0.0f;
set_fusion_settings(ahrs.get(), ahrs_sample_rate, ahrs_drift_correction);
// Start measuring the sample period from scratch and discard samples from before the reset
last_ahrs_update_time_us = 0;
imu_sample_count = 0;
}
void PadDevice::update_orientation(ps_move_data& move_data)
@@ -990,53 +1042,141 @@ void PadDevice::update_orientation(ps_move_data& move_data)
reset_orientation();
}
// Get elapsed time since last update
const u64 now_us = get_system_time();
const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
last_ahrs_update_time_us = now_us;
if (!queues_imu_samples)
{
// Use the current sensor values and the elapsed time since the last update
const u64 now_us = get_system_time();
const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
last_ahrs_update_time_us = now_us;
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
// Don't ask how the axis work. It's basically been trial and error.
// We need a valid sample period. Skip the first update after a reset.
if (elapsed_sec > 0.0f)
{
imu_samples[0] = { move_data.accelerometer, move_data.gyro };
imu_sample_count = 1;
imu_sample_delta_time = elapsed_sec;
}
}
// Feed each queued sample to the AHRS
const u32 sample_count = std::exchange(imu_sample_count, 0);
f32 elapsed_sec = 0.0f;
for (u32 i = 0; i < sample_count; i++)
{
if (update_ahrs(move_data, ::at32(imu_samples, i), imu_sample_delta_time))
{
elapsed_sec += imu_sample_delta_time;
}
}
if (elapsed_sec > 0.0f)
{
move_data.update_orientation(elapsed_sec);
}
}
bool PadDevice::update_ahrs(ps_move_data& move_data, const imu_sample& sample, f32 elapsed_sec)
{
if (!ahrs || !(elapsed_sec > 0.0f))
{
return false;
}
const ps_move_data::vect<3>& accel = sample.accelerometer;
const ps_move_data::vect<3>& gyro = sample.gyro;
// The sensor data in move_data uses the following device frame (see set_raw_orientation and the PS Move handler):
// x: right, y: forward (towards the sphere), z: up (buttons)
// The accelerometer and gyro share this frame.
// Fusion uses East-North-Up, so we can feed it the device frame as is. The identity orientation is then "flat, pointing forward",
// and the accelerometer measures +1g on the z axis at rest, which is what Fusion expects as gravity reference.
ensure(ahrs->convention == FusionConvention::FusionConventionEnu); // East-North-Up
const FusionVector accelerometer{
.axis {
.x = -move_data.accelerometer.x(),
.y = +move_data.accelerometer.y(),
.z = +move_data.accelerometer.z()
.x = accel.x(),
.y = accel.y(),
.z = accel.z()
}
};
const FusionVector gyroscope{
FusionVector gyroscope{
.axis {
.x = +PadHandlerBase::rad_to_degree(move_data.gyro.x()),
.y = +PadHandlerBase::rad_to_degree(move_data.gyro.z()),
.z = -PadHandlerBase::rad_to_degree(move_data.gyro.y())
.x = PadHandlerBase::rad_to_degree(gyro.x()),
.y = PadHandlerBase::rad_to_degree(gyro.y()),
.z = PadHandlerBase::rad_to_degree(gyro.z())
}
};
FusionVector magnetometer {};
if (move_data.magnetometer_enabled)
if (!std::isfinite(gyroscope.axis.x) || !std::isfinite(gyroscope.axis.y) || !std::isfinite(gyroscope.axis.z))
{
magnetometer = FusionVector{
.axis {
.x = move_data.magnetometer.x(),
.y = move_data.magnetometer.y(),
.z = move_data.magnetometer.z()
}
};
return false;
}
// Update Fusion
if (ahrs_drift_correction && !gyro_bias_initialized)
{
FusionBiasInitialise(&gyro_bias);
gyro_bias_initialized = true;
}
// Keep a backup in case the update yields an invalid orientation (e.g. due to garbage sensor data)
const FusionAhrs ahrs_backup = *ahrs;
const FusionBias gyro_bias_backup = gyro_bias;
// The startup gain ramp and the rejection timeout are calculated per sample, so the settings have to match the actual sample rate.
// Otherwise the startup period would depend on how often we get here.
// Applying the settings also resets the internal rejection state, so we only do it if the sample rate changed noticeably.
const f32 sample_rate = 1.0f / elapsed_sec;
ahrs_measured_sample_rate = (ahrs_measured_sample_rate > 0.0f) ? (ahrs_measured_sample_rate * 0.9f + sample_rate * 0.1f) : sample_rate;
if (std::abs(ahrs_measured_sample_rate - ahrs_sample_rate) > ahrs_sample_rate * 0.25f)
{
ahrs_sample_rate = ahrs_measured_sample_rate;
set_fusion_settings(ahrs.get(), ahrs_sample_rate, ahrs_drift_correction);
}
// Remove the gyro offset. The offset is learned while the device is stationary.
// The stationary period and the filter are also calculated per sample, so the settings have to match the sample rate as well.
if (ahrs_drift_correction)
{
if (gyro_bias.settings.sampleRate != ahrs_sample_rate)
{
FusionBiasSettings bias_settings = fusionBiasDefaultSettings;
bias_settings.sampleRate = ahrs_sample_rate;
bias_settings.stationaryThreshold = 10.0f;
FusionBiasSetSettings(&gyro_bias, &bias_settings);
}
gyroscope = FusionBiasUpdate(&gyro_bias, gyroscope);
}
// Update Fusion.
// We don't use the magnetometer. It would need a proper calibration and would make the heading absolute instead of relative to the calibration pose.
// Note: FusionAhrsUpdateNoMagnetometer would also lock the heading during the startup period, so we pass a zero vector instead.
FusionAhrsSetSamplePeriod(ahrs.get(), elapsed_sec);
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, magnetometer);
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, FusionVector{});
// Get quaternion
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(ahrs.get());
move_data.quaternion[0] = quaternion.array[1];
move_data.quaternion[1] = quaternion.array[2];
move_data.quaternion[2] = quaternion.array[3];
move_data.quaternion[3] = quaternion.array[0];
move_data.update_orientation(elapsed_sec);
if (!std::isfinite(quaternion.array[0]) || !std::isfinite(quaternion.array[1]) ||
!std::isfinite(quaternion.array[2]) || !std::isfinite(quaternion.array[3]))
{
// Discard this update and keep the last valid orientation
*ahrs = ahrs_backup;
gyro_bias = gyro_bias_backup;
return false;
}
// Convert the quaternion from the device frame (x: right, y: forward, z: up) to the cellGem frame (x: right, y: up, z: backward).
// The cellGem identity orientation is "facing the camera with buttons up", which is the same pose as our "flat, pointing forward".
// The change of basis is a proper rotation C with (x, y, z) -> (x, z, -y), so q' = C * q * C^-1 = (w, C * v).
// This is the same transform that ps move api uses for its OpenGL sensor basis.
move_data.quaternion[0] = quaternion.array[1]; // x = x
move_data.quaternion[1] = quaternion.array[3]; // y = z
move_data.quaternion[2] = -quaternion.array[2]; // z = -y
move_data.quaternion[3] = quaternion.array[0]; // w
return true;
}
+33 -2
View File
@@ -11,6 +11,7 @@
#endif
extern "C" {
#include "3rdparty/fusion/fusion/Fusion/FusionAhrs.h"
#include "3rdparty/fusion/fusion/Fusion/FusionBias.h"
}
#ifndef _MSC_VER
#pragma GCC diagnostic pop
@@ -50,11 +51,38 @@ public:
bool enable_player_leds{};
bool update_player_leds{true};
steady_clock::time_point last_reconnect_attempt{};
std::shared_ptr<FusionAhrs> ahrs; // Used to calculate quaternions from sensor data
u64 last_ahrs_update_time_us = 0; // Last ahrs update
bool ahrs_drift_correction = false; // Continuously correct the inclination using the accelerometer and estimate the gyro bias
f32 ahrs_sample_rate = 0.0f; // Sample rate that the AHRS settings were applied with
f32 ahrs_measured_sample_rate = 0.0f; // Smoothed measured sample rate
// Run-time estimation of the gyro offset (only used with drift correction).
// This is a sensor property, so it is kept across orientation resets. Reset it if a different device is connected.
FusionBias gyro_bias{};
bool gyro_bias_initialized = false;
// Sensor samples (accelerometer in G, gyro in rad/s) for the next orientation update.
// By default, update_orientation uses the current values in move_data and the time since the last update.
// Handlers that set queues_imu_samples queue their own samples instead (e.g. once per input report).
struct imu_sample
{
ps_move_data::vect<3> accelerometer{};
ps_move_data::vect<3> gyro{};
};
bool queues_imu_samples = false;
std::array<imu_sample, 2> imu_samples{};
u32 imu_sample_count = 0;
f32 imu_sample_delta_time = 0.0f; // Seconds per sample
void update_orientation(ps_move_data& move_data);
void reset_orientation();
private:
// Feeds one sensor sample to the AHRS and updates move_data.quaternion. Returns false if the sample was discarded.
bool update_ahrs(ps_move_data& move_data, const imu_sample& sample, f32 elapsed_sec);
};
struct pad_ensemble
@@ -231,11 +259,14 @@ public:
// input has to be [-1,1]. result will be [0,255]
static u16 ConvertAxis(f32 value);
// Convert analog stick angle to a value ranging from 0 to 255
static u8 ConvertAngleToU8(f32 angle, f32 distance_to_center);
// The DS3, (and i think xbox controllers) give a 'square-ish' type response, so that the corners will give (almost)max x/y instead of the ~30x30 from a perfect circle
// using a simple scale/sensitivity increase would *work* although it eats a chunk of our usable range in exchange
// this might be the best for now, in practice it seems to push the corners to max of 20x20, with a squircle_factor of ~4000
// This function assumes inX and inY is already in 0-255
static void ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor);
static std::tuple<f32, f32> ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor);
// u32 thumb_min = 0; // Unused. Make sure all handlers report 0+ values for sticks in get_button_values.
u32 thumb_max = 255;
@@ -273,7 +304,7 @@ public:
virtual pad_capabilities get_capabilities(const std::string& /*pad_id*/);
u16 NormalizeStickInput(u16 raw_value, s32 threshold, s32 multiplier, bool ignore_threshold = false) const;
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const;
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling, f32& angle, f32& distance_to_center) const;
void set_trigger_recognition_mode(trigger_recognition_mode mode) { m_trigger_recognition_mode = mode; }
virtual bool Init() { return true; }
+65 -12
View File
@@ -6,6 +6,32 @@ LOG_CHANNEL(skylander_log, "skylander");
sky_portal g_skyportal;
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size)
{
constexpr unsigned short CRC_CCITT_TABLE[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,
0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,
0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,
0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,
0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,
0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,
0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
u16 crc = init_value;
for (u32 i = 0; i < size; i++)
{
const u16 tmp = (crc >> 8) ^ buffer[i];
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
}
return crc;
}
void skylander::save()
{
if (!sky_file)
@@ -153,32 +179,41 @@ bool sky_portal::remove_skylander(u8 sky_num)
return false;
}
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file)
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot)
{
std::lock_guard lock(sky_mutex);
const u32 sky_serial = read_from_ptr<le_t<u32>>(data);
u8 found_slot = 0xFF;
// mimics spot retaining on the portal
for (u8 i = 0; i < 8; i++)
if (requested_slot >= 0 && requested_slot <= 7)
{
if ((skylanders[i].status & 1) == 0)
if ((skylanders[requested_slot].status & 1) == 0)
found_slot = static_cast<u8>(requested_slot);
}
else
{
// mimics spot retaining on the portal
for (u8 i = 0; i < 8; i++)
{
if (skylanders[i].last_id == sky_serial)
if ((skylanders[i].status & 1) == 0)
{
found_slot = i;
break;
}
if (skylanders[i].last_id == sky_serial)
{
found_slot = i;
break;
}
if (i < found_slot)
{
found_slot = i;
if (i < found_slot)
{
found_slot = i;
}
}
}
}
ensure(found_slot != 0xFF);
if (found_slot == 0xFF)
return 0xFF;
skylander& thesky = skylanders[found_slot];
memcpy(thesky.data.data(), data.data(), thesky.data.size());
@@ -191,6 +226,24 @@ u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file
return found_slot;
}
void sky_portal::get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant)
{
ensure(sky_num < 8);
std::lock_guard lock(sky_mutex);
const auto& s = skylanders[sky_num];
out_status = s.status;
if (s.status & 1)
{
out_id = read_from_ptr<le_t<u16>>(s.data, 0x10);
out_variant = read_from_ptr<le_t<u16>>(s.data, 0x1C);
}
else
{
out_id = 0;
out_variant = 0;
}
}
usb_device_skylander::usb_device_skylander(const std::array<u8, 7>& location)
: usb_device_emulated(location)
{
+4 -1
View File
@@ -27,7 +27,8 @@ public:
void write_block(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
bool remove_skylander(u8 sky_num);
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file);
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot = -1);
void get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant);
protected:
shared_mutex sky_mutex;
@@ -41,6 +42,8 @@ protected:
extern sky_portal g_skyportal;
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size);
class usb_device_skylander : public usb_device_emulated
{
public:
+354
View File
@@ -0,0 +1,354 @@
#include "stdafx.h"
#include "SkylanderPortalIPC.h"
#include "SkylanderPortalIPC_config.h"
#ifdef _WIN32
#include <WinSock2.h>
#include <ws2tcpip.h>
using sky_socket_t = SOCKET;
static constexpr sky_socket_t sky_invalid_socket = INVALID_SOCKET;
static int sky_recv(sky_socket_t s, char* buf, int len) { return recv(s, buf, len, 0); }
static int sky_send_some(sky_socket_t s, const char* buf, int len) { return send(s, buf, len, 0); }
static void sky_close(sky_socket_t s) { closesocket(s); }
static bool sky_interrupted() { return WSAGetLastError() == WSAEINTR; }
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
{
const DWORD timeout = timeout_ms;
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) == 0;
}
#else
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <poll.h>
#include <unistd.h>
using sky_socket_t = int;
static constexpr sky_socket_t sky_invalid_socket = -1;
static int sky_recv(sky_socket_t s, char* buf, int len) { return static_cast<int>(read(s, buf, len)); }
static int sky_send_some(sky_socket_t s, const char* buf, int len) { return static_cast<int>(write(s, buf, len)); }
static void sky_close(sky_socket_t s) { close(s); }
static bool sky_interrupted() { return errno == EINTR; }
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
{
timeval timeout{};
timeout.tv_sec = static_cast<time_t>(timeout_ms / 1000);
timeout.tv_usec = static_cast<suseconds_t>(timeout_ms % 1000 * 1000);
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0;
}
#endif
#include "Skylander.h"
#include "3rdparty/pine/pine_server.h"
#include <chrono>
LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC");
static constexpr usz max_command_size = 4096;
static constexpr u32 client_timeout_ms = 1000;
static bool is_absolute_local_path(std::string_view path)
{
#ifdef _WIN32
// Requiring a drive letter also rules out network (\\server\share) and device (\\.\) paths
const bool has_drive_letter = path.size() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':';
return has_drive_letter && (path[2] == '\\' || path[2] == '/');
#else
return path.starts_with('/');
#endif
}
static std::string handle_load(int slot, const std::string& path)
{
if (slot < -1 || slot > 7)
return "error invalid slot\n";
if (!is_absolute_local_path(path))
return "error path must be absolute\n";
std::array<u8, 0x40 * 0x10> buf{};
// Checked before opening, so that devices and pipes are rejected without ever being opened
fs::stat_t info{};
if (!fs::get_stat(path, info) || info.is_directory || info.size != buf.size())
return "error not a skylander file\n";
fs::file sky_file(path, fs::read + fs::write + fs::lock);
if (!sky_file)
return "error cannot open file\n";
if (sky_file.read(buf.data(), buf.size()) != buf.size())
return "error file too small\n";
if (read_from_ptr<le_t<u16>>(buf, 0x1E) != skylander_crc16(0xFFFF, buf.data(), 0x1E))
return "error not a skylander file\n";
const u8 result = g_skyportal.load_skylander(buf, std::move(sky_file), slot);
if (result == 0xFF)
return "error no free slot\n";
return "ok " + std::to_string(result) + "\n";
}
static std::string handle_remove(int slot)
{
if (slot < 0 || slot > 7)
return "error invalid slot\n";
if (!g_skyportal.remove_skylander(static_cast<u8>(slot)))
return "error slot empty\n";
return "ok\n";
}
static std::string handle_status()
{
std::string result;
for (u8 i = 0; i < 8; i++)
{
u8 status;
u16 id, variant;
g_skyportal.get_figure_info(i, status, id, variant);
if (status & 1)
result += "slot" + std::to_string(i) + " loaded " + std::to_string(id) + " " + std::to_string(variant) + "\n";
else
result += "slot" + std::to_string(i) + " empty\n";
}
result += "ok\n";
return result;
}
static std::string handle_clear()
{
for (u8 i = 0; i < 8; i++)
g_skyportal.remove_skylander(i);
return "ok\n";
}
static bool parse_int(const std::string& s, int& out)
{
if (s.empty())
return false;
char* end;
errno = 0;
const long val = std::strtol(s.c_str(), &end, 10);
if (end == s.c_str() || *end != '\0' || errno != 0)
return false;
out = static_cast<int>(val);
return true;
}
static std::string process_command(const std::string& line)
{
if (line.empty())
return "error empty command\n";
const usz first_space = line.find(' ');
const std::string cmd = line.substr(0, first_space);
if (cmd == "status")
return handle_status();
if (cmd == "clear")
return handle_clear();
if (cmd == "remove")
{
if (first_space == std::string::npos)
return "error missing slot\n";
int slot;
if (!parse_int(line.substr(first_space + 1), slot))
return "error invalid slot\n";
return handle_remove(slot);
}
if (cmd == "load")
{
if (first_space == std::string::npos)
return "error missing arguments\n";
const std::string rest = line.substr(first_space + 1);
const usz second_space = rest.find(' ');
if (second_space == std::string::npos)
return "error missing path\n";
int slot;
if (!parse_int(rest.substr(0, second_space), slot))
return "error invalid arguments\n";
const std::string path = rest.substr(second_space + 1);
return handle_load(slot, path);
}
return "error unknown command\n";
}
static bool sky_send(sky_socket_t s, std::string_view data)
{
while (!data.empty())
{
const int sent = sky_send_some(s, data.data(), ::narrow<int>(data.size()));
if (sent > 0)
data.remove_prefix(sent);
else if (sent < 0 && sky_interrupted())
continue;
else
return false;
}
return true;
}
static bool read_command(sky_socket_t client_sock, std::string& line)
{
if (!sky_set_recv_timeout(client_sock, client_timeout_ms))
return false;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(client_timeout_ms);
while (line.size() < max_command_size && std::chrono::steady_clock::now() < deadline)
{
char ch;
const int received = sky_recv(client_sock, &ch, 1);
if (received == 0)
return true;
if (received < 0)
{
if (sky_interrupted())
continue;
return false;
}
if (ch == '\n' || ch == '\r')
return true;
line += ch;
}
return false;
}
void SkylanderPortalIPCServer::operator()()
{
const int port = g_cfg_sky_ipc.get_port();
#ifdef _WIN32
WSADATA wsa{};
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
skylander_ipc_log.error("Cannot initialize winsock");
return;
}
const sky_socket_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == sky_invalid_socket)
{
skylander_ipc_log.error("Cannot create socket");
WSACleanup();
return;
}
if (const char* failure = pine::bind_and_listen(listen_sock, static_cast<u16>(port)))
{
skylander_ipc_log.error("Cannot start server on port %d: %s", port, failure);
sky_close(listen_sock);
WSACleanup();
return;
}
skylander_ipc_log.notice("Server started on port %d", port);
#else
const std::string socket_path = pine::get_socket_path("rpcs3.skylanders.sock", port, g_cfg_sky_ipc.sky_ipc_port.def);
const sky_socket_t listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_sock == sky_invalid_socket)
{
skylander_ipc_log.error("Cannot create socket");
return;
}
if (const char* failure = pine::bind_and_listen(listen_sock, socket_path))
{
skylander_ipc_log.error("Cannot start server at %s: %s", socket_path, failure);
sky_close(listen_sock);
return;
}
skylander_ipc_log.notice("Server started at %s", socket_path);
#endif
while (thread_ctrl::state() != thread_state::aborting)
{
pollfd pfd{};
pfd.fd = listen_sock;
pfd.events = POLLIN;
#ifdef _WIN32
const int poll_result = WSAPoll(&pfd, 1, 10);
#else
const int poll_result = poll(&pfd, 1, 10);
#endif
if (poll_result < 0 && !sky_interrupted())
{
skylander_ipc_log.error("Waiting for connections failed, stopping server");
break;
}
if (poll_result <= 0)
continue;
const sky_socket_t client_sock = accept(listen_sock, nullptr, nullptr);
if (client_sock == sky_invalid_socket)
continue;
std::string line;
if (read_command(client_sock, line) && !line.empty())
{
if (!sky_send(client_sock, process_command(line)))
skylander_ipc_log.warning("Failed to send the response");
}
sky_close(client_sock);
}
sky_close(listen_sock);
#ifdef _WIN32
WSACleanup();
#else
unlink(socket_path.c_str());
#endif
skylander_ipc_log.notice("Server stopped");
}
SkylanderPortalIPCServer& SkylanderPortalIPCServer::operator=(thread_state)
{
return *this;
}
SkylanderPortalIPCServerManager::SkylanderPortalIPCServerManager(bool enabled)
{
set_server_enabled(enabled);
}
void SkylanderPortalIPCServerManager::set_server_enabled(bool enabled)
{
if (enabled)
{
const int port = g_cfg_sky_ipc.get_port();
if (!m_server || port != m_old_port)
{
skylander_ipc_log.notice("Starting server with port %d", port);
m_server = std::make_unique<server_thread>();
m_old_port = port;
}
}
else if (m_server)
{
skylander_ipc_log.notice("Stopping server");
m_server.reset();
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "Utilities/Thread.h"
struct SkylanderPortalIPCServer
{
static constexpr auto thread_name = "Skylanders Portal IPC"sv;
void operator()();
SkylanderPortalIPCServer& operator=(thread_state);
};
class SkylanderPortalIPCServerManager
{
using server_thread = named_thread<SkylanderPortalIPCServer>;
std::unique_ptr<server_thread> m_server;
int m_old_port = 0;
public:
explicit SkylanderPortalIPCServerManager(bool enabled);
void set_server_enabled(bool enabled);
};
@@ -0,0 +1,66 @@
#include "stdafx.h"
#include "SkylanderPortalIPC_config.h"
cfg_sky_ipc g_cfg_sky_ipc;
LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC");
void cfg_sky_ipc::load()
{
const std::string path = cfg_sky_ipc::get_path();
fs::file cfg_file(path, fs::read);
if (cfg_file)
{
skylander_ipc_log.notice("Loading Skylanders IPC config. Path: %s", path);
from_string(cfg_file.to_string());
}
else
{
skylander_ipc_log.notice("Skylanders IPC config missing. Using default settings. Path: %s", path);
from_default();
}
}
void cfg_sky_ipc::save() const
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir(true);
if (!fs::create_path(path_to_cfg))
{
skylander_ipc_log.error("Could not create path: %s", path_to_cfg);
}
#endif
const std::string path = cfg_sky_ipc::get_path();
if (!cfg::node::save(path))
{
skylander_ipc_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error);
}
}
std::string cfg_sky_ipc::get_path()
{
return fs::get_config_dir(true) + "sky_ipc.yml";
}
bool cfg_sky_ipc::get_server_enabled() const
{
return sky_ipc_server_enabled.get();
}
int cfg_sky_ipc::get_port() const
{
return sky_ipc_port;
}
void cfg_sky_ipc::set_server_enabled(const bool enabled)
{
this->sky_ipc_server_enabled.set(enabled);
}
void cfg_sky_ipc::set_port(const int port)
{
this->sky_ipc_port.set(port);
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Utilities/Config.h"
struct cfg_sky_ipc : cfg::node
{
cfg::_bool sky_ipc_server_enabled{ this, "Skylanders IPC Server enabled", false };
cfg::_int<1025, 65535> sky_ipc_port{ this, "Skylanders IPC Port", 28013 };
void load();
void save() const;
bool get_server_enabled() const;
int get_port() const;
void set_server_enabled(const bool enabled);
void set_port(const int port);
private:
static std::string get_path();
};
extern cfg_sky_ipc g_cfg_sky_ipc;
+5 -11
View File
@@ -350,7 +350,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
mouse_handler.Init(4);
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
if (mouse_index >= mouse_handler.GetMice().size())
{
prepare_data(&ts, buf);
@@ -361,17 +361,11 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
cfg->handle_input(mouse_data, input_callback);
ts.trigger = ts.btn_trigger ? 0xff : 0x00;
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
{
prepare_data(&ts, buf);
return;
}
s32 led_lx = 0x3ff - (TSE_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT)) + TSE_CALIB_DIST);
s32 led_rx = 0x3ff - (TSE_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT)) - TSE_CALIB_DIST);
s32 led_lx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) + TSE_CALIB_DIST);
s32 led_rx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) - TSE_CALIB_DIST);
s32 led_ly = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP) / mouse_data.y_max);
s32 led_ry = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP) / mouse_data.y_max);
s32 led_ly = TSE_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP));
s32 led_ry = TSE_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP));
u8 detect_l = 0x2, detect_r = 0x2; // 0x2 = led detected / 0xf = undetected
+5 -11
View File
@@ -374,7 +374,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
mouse_handler.Init(4);
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
if (mouse_index >= mouse_handler.GetMice().size())
{
prepare_data(&ts, buf);
@@ -386,17 +386,11 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
ts.trigger = ts.btn_trigger ? 0xff : 0x00;
ts.heartrate = ts.btn_heartrate ? get_heartrate_sensor_value(60) : 0;
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
{
prepare_data(&ts, buf);
return;
}
s32 led_lx = 0x3ff - (TSF_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT)) + TSF_CALIB_DIST);
s32 led_rx = 0x3ff - (TSF_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT)) - TSF_CALIB_DIST);
s32 led_lx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) + TSF_CALIB_DIST);
s32 led_rx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) - TSF_CALIB_DIST);
s32 led_ly = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max);
s32 led_ry = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max);
s32 led_ly = TSF_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP));
s32 led_ry = TSF_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP));
u8 detect_l = 0x2, detect_r = 0x2; // 0x2 = led detected / 0xf = undetected
+2 -4
View File
@@ -76,7 +76,8 @@ public:
for (const auto& n : get_nodes())
{
init_button(static_cast<cfg_pad_btn<T>*>(n));
if (auto* pbtn = dynamic_cast<cfg_pad_btn<T>*>(n))
init_button(pbtn);
}
}
@@ -108,9 +109,6 @@ public:
if (max_value == 1023) return value & 0x03FF;
return static_cast<u16>(std::clamp(1023.0f * (static_cast<f32>(std::min(value, max_value)) / ensure(max_value)), 0.0f, 1023.0f));
}
private:
};
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(const input_value&, bool&)>& func) const
+8 -15
View File
@@ -109,28 +109,21 @@ int evdev_gun_handler::get_button(u32 gunno, gun_button button) const
return 0;
}
int evdev_gun_handler::get_axis_x(u32 gunno) const
f32 evdev_gun_handler::get_axis(u32 gunno, int code) const
{
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_X);
return axis.value - axis.min;
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, code);
const int range = axis.max - axis.min;
return range == 0 ? 0.0f : std::clamp((axis.value - axis.min) / static_cast<f32>(range), 0.0f, 1.0f);
}
int evdev_gun_handler::get_axis_y(u32 gunno) const
f32 evdev_gun_handler::get_axis_x(u32 gunno) const
{
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_Y);
return axis.value - axis.min;
return get_axis(gunno, ABS_X);
}
int evdev_gun_handler::get_axis_x_max(u32 gunno) const
f32 evdev_gun_handler::get_axis_y(u32 gunno) const
{
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_X);
return axis.max - axis.min;
}
int evdev_gun_handler::get_axis_y_max(u32 gunno) const
{
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_Y);
return axis.max - axis.min;
return get_axis(gunno, ABS_Y);
}
void evdev_gun_handler::poll(u32 index)
+4 -4
View File
@@ -30,16 +30,16 @@ public:
bool is_init() const;
u32 get_num_guns() const;
int get_button(u32 gunno, gun_button button) const;
int get_axis_x(u32 gunno) const;
int get_axis_y(u32 gunno) const;
int get_axis_x_max(u32 gunno) const;
int get_axis_y_max(u32 gunno) const;
f32 get_axis_x(u32 gunno) const;
f32 get_axis_y(u32 gunno) const;
void poll(u32 index);
shared_mutex mutex;
private:
f32 get_axis(u32 gunno, int code) const;
atomic_t<bool> m_is_init{false};
struct udev* m_udev = nullptr;
+9
View File
@@ -53,6 +53,9 @@ void fmt_class_string<pad_button>::format(std::string& out, u64 arg)
case pad_button::mouse_button_6: return "Mouse Button 6";
case pad_button::mouse_button_7: return "Mouse Button 7";
case pad_button::mouse_button_8: return "Mouse Button 8";
case pad_button::mouse: return "Mouse";
case pad_button::left_stick: return "Left Stick";
case pad_button::right_stick: return "Right Stick";
}
return unknown;
@@ -106,6 +109,9 @@ u32 pad_button_offset(pad_button button)
case pad_button::mouse_button_6:
case pad_button::mouse_button_7:
case pad_button::mouse_button_8:
case pad_button::mouse:
case pad_button::left_stick:
case pad_button::right_stick:
return 0;
}
return 0;
@@ -158,6 +164,9 @@ u32 pad_button_keycode(pad_button button)
case pad_button::mouse_button_6: return 6;
case pad_button::mouse_button_7: return 7;
case pad_button::mouse_button_8: return 8;
case pad_button::mouse: return 0;
case pad_button::left_stick: return 0;
case pad_button::right_stick: return 0;
}
return 0;
}
+6
View File
@@ -60,6 +60,11 @@ enum class pad_button : u8
mouse_button_6,
mouse_button_7,
mouse_button_8,
// Special "buttons" that map behaviour
mouse,
left_stick,
right_stick
};
u32 pad_button_offset(pad_button button);
@@ -542,6 +547,7 @@ struct Pad
std::array<AnalogStick, 4> m_sticks{};
std::array<AnalogSensor, 4> m_sensors{};
std::array<VibrateMotor, 2> m_vibrate_motors{};
std::array<u8, 2> m_angles{};
std::vector<ButtonExternal> m_buttons_external;
std::array<AnalogStickExternal, 4> m_sticks_external{};
+2
View File
@@ -62,6 +62,8 @@ struct ps_move_data
bool calibration_requested = false;
bool calibration_succeeded = false;
bool orientation_reset_requested = false; // Reset the orientation to the default (facing the camera). See cellGemSetYaw.
bool magnetometer_enabled = false;
bool orientation_enabled = false;
+1 -1
View File
@@ -189,7 +189,7 @@ void usb_device_passthrough::read_descriptors()
for (u8 index = 0; index < device._device.bNumConfigurations; index++)
{
std::array<u8, 1000> buf{};
const int ssize = libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_STANDARD | +LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_GET_DESCRIPTOR, 0x0200 | index, 0, buf.data(), buf.size(), 0);
const int ssize = libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_STANDARD | +LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_GET_DESCRIPTOR, 0x0200 | index, 0, buf.data(), static_cast<u16>(buf.size()), 0);
if (ssize < 0)
{
sys_usbd.fatal("Couldn't get the config from the device: %d(%s)", ssize, libusb_error_name(ssize));
+97 -40
View File
@@ -2,9 +2,11 @@
#include "stdafx.h"
#include "usio.h"
#include "MouseHandler.h"
#include "Input/pad_thread.h"
#include "Emu/Io/usio_config.h"
#include "Emu/IdManager.h"
#include "Emu/system_config.h"
LOG_CHANNEL(usio_log, "USIO");
@@ -33,6 +35,7 @@ void fmt_class_string<usio_btn>::format(std::string& out, u64 arg)
case usio_btn::tekken_button4: return "Tekken Button 4";
case usio_btn::tekken_button5: return "Tekken Button 5";
case usio_btn::card_tapping: return "Card Tapping";
case usio_btn::gun: return "Gun";
case usio_btn::count: return "Count";
}
@@ -213,7 +216,7 @@ void usb_device_usio::translate_input_taiko()
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_pad_thread();
std::vector<u8> input_buf(0x60);
response.assign(0x60, 0);
constexpr le_t<u16> c_hit = 0x1800;
le_t<u16> digital_input = 0;
@@ -259,19 +262,19 @@ void usb_device_usio::translate_input_taiko()
break;
case usio_btn::taiko_hit_side_left:
if (value.pressed)
std::memcpy(input_buf.data() + 32 + offset, &c_hit, sizeof(u16));
std::memcpy(response.data() + 32 + offset, &c_hit, sizeof(u16));
break;
case usio_btn::taiko_hit_center_right:
if (value.pressed)
std::memcpy(input_buf.data() + 36 + offset, &c_hit, sizeof(u16));
std::memcpy(response.data() + 36 + offset, &c_hit, sizeof(u16));
break;
case usio_btn::taiko_hit_side_right:
if (value.pressed)
std::memcpy(input_buf.data() + 38 + offset, &c_hit, sizeof(u16));
std::memcpy(response.data() + 38 + offset, &c_hit, sizeof(u16));
break;
case usio_btn::taiko_hit_center_left:
if (value.pressed)
std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16));
std::memcpy(response.data() + 34 + offset, &c_hit, sizeof(u16));
break;
case usio_btn::card_tapping:
if (value.pressed)
@@ -292,10 +295,8 @@ void usb_device_usio::translate_input_taiko()
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
translate_from_pad(i, i);
std::memcpy(input_buf.data(), &digital_input, sizeof(u16));
std::memcpy(input_buf.data() + 16, &m_io_status[0].coin_counter, sizeof(u16));
response = std::move(input_buf);
std::memcpy(response.data(), &digital_input, sizeof(u16));
std::memcpy(response.data() + 16, &m_io_status[0].coin_counter, sizeof(u16));
}
void usb_device_usio::translate_input_tekken()
@@ -303,45 +304,54 @@ void usb_device_usio::translate_input_tekken()
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_pad_thread();
std::vector<u8> input_buf(0x180);
response.assign(0x180, 0);
le_t<u64> digital_input[2]{};
le_t<u16> digital_input_lm = 0;
const auto translate_from_pad = [&](usz pad_number, usz player)
{
const usz shift = (player % 2) * 24ULL;
auto& status = m_io_status[player / 2];
auto& input = digital_input[player / 2];
auto& mouse_handler = g_fxo->get<MouseHandlerBase>();
std::lock_guard mouse_lock(mouse_handler.mutex);
mouse_handler.Init(static_cast<u32>(g_cfg_usio.players.size()));
if (const auto& pad = ::at32(handler->GetPads(), pad_number); pad->is_connected() && !pad->is_copilot() && is_input_allowed())
const auto translate_input = [&](usz player)
{
if (!is_input_allowed())
{
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
cfg->handle_input(pad, false, [&](const auto& value, bool& /*abort*/)
return;
}
const usz player_index = player % 2;
const usz shift = (player_index) * 24ULL;
const usz io_index = player / 2;
auto& status = m_io_status[io_index];
auto& input = digital_input[io_index];
const auto& cfg = ::at32(g_cfg_usio.players, player);
const auto input_callback = [&](const auto& value, bool& /*abort*/)
{
switch (value.btn)
{
case usio_btn::test:
if (player % 2 != 0)
if (player_index != 0)
break;
if (value.pressed && !status.test_key_pressed) // Solve the need to hold the Test button
status.test_on = !status.test_on;
status.test_key_pressed = value.pressed;
break;
case usio_btn::coin:
if (player % 2 != 0)
if (player_index != 0)
break;
if (value.pressed && !status.coin_key_pressed) // Ensure only one coin is inserted each time the Coin button is pressed
status.coin_counter++;
status.coin_key_pressed = value.pressed;
break;
case usio_btn::service:
if (player % 2 == 0 && value.pressed)
if (player_index == 0 && value.pressed)
input |= 0x4000;
break;
case usio_btn::enter:
if (value.pressed)
{
input |= 0x800000ULL << shift;
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x200ULL : 0x800000ULL) << shift;
if (player == 0)
digital_input_lm |= 0x800;
}
@@ -349,7 +359,7 @@ void usb_device_usio::translate_input_tekken()
case usio_btn::up:
if (value.pressed)
{
input |= 0x200000ULL << shift;
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x2000ULL : 0x200000ULL) << shift;
if (player == 0)
digital_input_lm |= 0x200;
}
@@ -357,7 +367,7 @@ void usb_device_usio::translate_input_tekken()
case usio_btn::down:
if (value.pressed)
{
input |= 0x100000ULL << shift;
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x1000ULL : 0x100000ULL) << shift;
if (player == 0)
digital_input_lm |= 0x400;
}
@@ -365,7 +375,7 @@ void usb_device_usio::translate_input_tekken()
case usio_btn::left:
if (value.pressed)
{
input |= 0x80000ULL << shift;
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x800000ULL : 0x100000ULL) : (0x80000ULL << shift);
if (player == 0)
digital_input_lm |= 0x2000;
}
@@ -373,12 +383,12 @@ void usb_device_usio::translate_input_tekken()
case usio_btn::right:
if (value.pressed)
{
input |= 0x40000ULL << shift;
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x400000ULL : 0x80000ULL) : (0x40000ULL << shift);
if (player == 0)
digital_input_lm |= 0x4000;
}
break;
case usio_btn::tekken_button1:
case usio_btn::tekken_button1: // or "2D / 3D Switch" button for shooter games
if (value.pressed)
{
input |= 0x20000ULL << shift;
@@ -386,9 +396,9 @@ void usb_device_usio::translate_input_tekken()
digital_input_lm |= 0x100;
}
break;
case usio_btn::tekken_button2:
case usio_btn::tekken_button2: // or "Start" button for shooter games
if (value.pressed)
input |= 0x10000ULL << shift;
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x200000ULL : 0x40000ULL) : (0x10000ULL << shift);
break;
case usio_btn::tekken_button3:
if (value.pressed)
@@ -409,10 +419,57 @@ void usb_device_usio::translate_input_tekken()
default:
break;
}
});
};
const auto write_gun_data = [&](u16 x, u16 y)
{
const le_t<u16> positions[2] = {x, y};
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 32 + player_index * sizeof(u32), &positions, sizeof(u32));
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 41 + player_index * sizeof(s16), &status.vital_sensors[player_index], sizeof(s8));
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 48, &status.wheel_rotation, sizeof(s8));
};
if (const auto& pad = ::at32(handler->GetPads(), player); pad->is_connected() && !pad->is_copilot())
{
cfg->handle_input(pad, false, input_callback);
const bool is_ctrl_ls = cfg->gun == pad_button::left_stick;
const bool is_ctrl_rs = cfg->gun == pad_button::right_stick;
if (is_ctrl_ls || is_ctrl_rs)
{
const s8 angle = pad->m_angles[is_ctrl_ls ? 0 : 1];
const s8 angle_delta = angle - status.vital_sensors[player_index];
status.vital_sensors[player_index] = angle;
status.wheel_rotation += angle_delta; // Multiple players can collaborate on turning the wheel
write_gun_data(static_cast<u16>(pad->m_sticks[is_ctrl_ls ? 0 : 2].m_value * USHRT_MAX / 0xff), static_cast<u16>(pad->m_sticks[is_ctrl_ls ? 1 : 3].m_value * USHRT_MAX / 0xff));
}
}
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : player;
if (mouse_index < mouse_handler.GetMice().size())
{
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
cfg->handle_input(mouse_data, input_callback);
if (cfg->gun == pad_button::mouse)
{
s8 current_wheel = 0;
MouseDataList& data_list = mouse_handler.GetDataList(mouse_index);
if (!data_list.empty())
{
const MouseData& current_data = data_list.front();
current_wheel = current_data.wheel * 0x10;
data_list.pop_front();
}
status.vital_sensors[player_index] += current_wheel;
status.wheel_rotation += current_wheel; // Multiple players can collaborate on turning the wheel
write_gun_data(static_cast<u16>(mouse_data.x_pos * USHRT_MAX), static_cast<u16>(mouse_data.y_pos * USHRT_MAX));
}
}
if (player % 2 == 0 && status.test_on)
if (player_index == 0 && status.test_on)
{
input |= 0x80;
if (player == 0)
@@ -423,19 +480,19 @@ void usb_device_usio::translate_input_tekken()
for (usz i = 0; i < m_io_status.size(); i++)
m_io_status[i].card_tapped = false;
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
translate_from_pad(i, i);
{
translate_input(i);
}
for (usz i = 0; i < 2; i++)
{
std::memcpy(input_buf.data() - i * 0x80 + 0x100, &digital_input[i], sizeof(u64));
std::memcpy(input_buf.data() - i * 0x80 + 0x100 + 0x10, &m_io_status[i].coin_counter, sizeof(u16));
std::memcpy(response.data() - i * 0x80 + 0x100, &digital_input[i], sizeof(u64));
std::memcpy(response.data() - i * 0x80 + 0x100 + 0x10, &m_io_status[i].coin_counter, sizeof(u16));
}
std::memcpy(input_buf.data(), &digital_input_lm, sizeof(u16));
std::memcpy(response.data(), &digital_input_lm, sizeof(u16));
input_buf[2] = 0b00010000; // DIP switches, 8 in total
response = std::move(input_buf);
response[2] = 0b00010000; // DIP switches, 8 in total
}
void usb_device_usio::emulate_card_reader(std::vector<u8>& buf, u16 reg)
@@ -475,8 +532,8 @@ void usb_device_usio::emulate_card_reader(std::vector<u8>& buf, u16 reg)
case 0x7800:
{
reader_index = reg == 0x7000 ? 0 : 1;
buf = std::move(pending_response[reader_index]);
pending_response[reader_index].clear(); // Ensure its empty state after being moved
buf.assign(pending_response[reader_index].begin(), pending_response[reader_index].end());
pending_response[reader_index].clear();
break;
}
case 0x7400:
+2
View File
@@ -39,6 +39,8 @@ private:
bool card_tapped = false;
le_t<u16> coin_counter = 0;
usz card_index = 0;
s8 vital_sensors[2] = {0, 0};
s8 wheel_rotation = 0;
};
std::array<io_status, 2> m_io_status;
+11 -8
View File
@@ -23,6 +23,8 @@ enum class usio_btn
tekken_button5,
card_tapping,
gun,
count
};
@@ -31,8 +33,8 @@ struct cfg_usio final : public emulated_pad_config<usio_btn>
cfg_usio(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
cfg_pad_btn<usio_btn> test{ this, "Test", usio_btn::test, pad_button::select };
cfg_pad_btn<usio_btn> coin{ this, "Coin", usio_btn::coin, pad_button::L3 };
cfg_pad_btn<usio_btn> service{this, "Service", usio_btn::service, pad_button::R3};
cfg_pad_btn<usio_btn> coin{ this, "Coin", usio_btn::coin, pad_button::L2 };
cfg_pad_btn<usio_btn> service{ this, "Service", usio_btn::service, pad_button::R2 };
cfg_pad_btn<usio_btn> enter{ this, "Enter/Start", usio_btn::enter, pad_button::start };
cfg_pad_btn<usio_btn> up{ this, "Up", usio_btn::up, pad_button::dpad_up };
cfg_pad_btn<usio_btn> down{ this, "Down", usio_btn::down, pad_button::dpad_down };
@@ -42,12 +44,13 @@ struct cfg_usio final : public emulated_pad_config<usio_btn>
cfg_pad_btn<usio_btn> taiko_hit_side_right{ this, "Taiko Hit Side Right", usio_btn::taiko_hit_side_right, pad_button::circle };
cfg_pad_btn<usio_btn> taiko_hit_center_left{ this, "Taiko Hit Center Left", usio_btn::taiko_hit_center_left, pad_button::triangle };
cfg_pad_btn<usio_btn> taiko_hit_center_right{ this, "Taiko Hit Center Right", usio_btn::taiko_hit_center_right, pad_button::cross };
cfg_pad_btn<usio_btn> tekken_button1{this, "Tekken Button 1", usio_btn::tekken_button1, pad_button::square};
cfg_pad_btn<usio_btn> tekken_button2{this, "Tekken Button 2", usio_btn::tekken_button2, pad_button::triangle};
cfg_pad_btn<usio_btn> tekken_button3{this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross};
cfg_pad_btn<usio_btn> tekken_button4{this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle};
cfg_pad_btn<usio_btn> tekken_button5{this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1};
cfg_pad_btn<usio_btn> card_tapping{this, "Card Tapping", usio_btn::card_tapping, pad_button::L1};
cfg_pad_btn<usio_btn> tekken_button1{ this, "Tekken Button 1", usio_btn::tekken_button1, pad_button::square};
cfg_pad_btn<usio_btn> tekken_button2{ this, "Tekken Button 2", usio_btn::tekken_button2, pad_button::triangle };
cfg_pad_btn<usio_btn> tekken_button3{ this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross };
cfg_pad_btn<usio_btn> tekken_button4{ this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle };
cfg_pad_btn<usio_btn> tekken_button5{ this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1 };
cfg_pad_btn<usio_btn> card_tapping{ this, "Card Tapping", usio_btn::card_tapping, pad_button::L1 };
cfg_pad_btn<usio_btn> gun{ this, "Gun", usio_btn::gun, pad_button::mouse };
};
struct cfg_usios final : public emulated_pads_config<cfg_usio, 4>
+58
View File
@@ -0,0 +1,58 @@
#include "stdafx.h"
#include "wiimote_config.h"
#include "Utilities/File.h"
LOG_CHANNEL(wiimote_log, "Wiimote");
template <>
void fmt_class_string<wiimote_button>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](wiimote_button value)
{
switch (value)
{
case wiimote_button::None: return "None";
case wiimote_button::Left: return "Left";
case wiimote_button::Right: return "Right";
case wiimote_button::Down: return "Down";
case wiimote_button::Up: return "Up";
case wiimote_button::Plus: return "Plus";
case wiimote_button::Two: return "Two";
case wiimote_button::One: return "One";
case wiimote_button::B: return "B";
case wiimote_button::A: return "A";
case wiimote_button::Minus: return "Minus";
case wiimote_button::Home: return "Home";
}
return unknown;
});
}
cfg_wiimote& get_wiimote_config()
{
static cfg_wiimote instance;
return instance;
}
cfg_wiimote::cfg_wiimote()
: cfg::node()
, path(fs::get_config_dir(true) + "wiimote.yml")
{
}
bool cfg_wiimote::load()
{
if (fs::file f{path, fs::read})
{
return cfg::node::from_string(f.to_string());
}
return false;
}
void cfg_wiimote::save() const
{
if (!cfg::node::save(path))
{
wiimote_log.error("Failed to save wiimote config to '%s'", path);
}
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "Utilities/Config.h"
#include "Input/wiimote_types.h"
struct cfg_wiimote : cfg::node
{
cfg_wiimote();
bool load();
void save() const;
cfg::_bool use_for_guncon{ this, "UseForGunCon", true };
struct node_guncon_mapping : cfg::node
{
node_guncon_mapping(cfg::node* _parent) : cfg::node(_parent, "GunCon Mapping") {}
cfg::_enum<wiimote_button> trigger{ this, "Trigger", wiimote_button::B };
cfg::_enum<wiimote_button> a1{ this, "A1", wiimote_button::A };
cfg::_enum<wiimote_button> a2{ this, "A2", wiimote_button::Minus };
cfg::_enum<wiimote_button> a3{ this, "A3", wiimote_button::Left };
cfg::_enum<wiimote_button> b1{ this, "B1", wiimote_button::One };
cfg::_enum<wiimote_button> b2{ this, "B2", wiimote_button::Two };
cfg::_enum<wiimote_button> b3{ this, "B3", wiimote_button::Home };
cfg::_enum<wiimote_button> c1{ this, "C1", wiimote_button::Plus };
cfg::_enum<wiimote_button> c2{ this, "C2", wiimote_button::Right };
cfg::_enum<wiimote_button> b1_alt{ this, "B1_Alt", wiimote_button::Up };
cfg::_enum<wiimote_button> b2_alt{ this, "B2_Alt", wiimote_button::Down };
} guncon_mapping{ this };
const std::string path;
};
cfg_wiimote& get_wiimote_config();
+52 -42
View File
@@ -1185,6 +1185,39 @@ namespace vm
bool block_t::try_alloc(u32 addr, u64 bflags, u32 size, std::shared_ptr<utils::shm>&& shm) const
{
if (!size)
{
return false;
}
if (this->flags & stack_guarded)
{
ensure(this->size >= 0x100000);
if (addr < this->addr + 4096 || addr + size - 1 >= this->addr + this->size - 4096 - 1)
{
return false;
}
// Protect guard pages
if (g_pages[addr / 4096 - 1] || g_pages[addr / 4096 + size / 4096])
{
return false;
}
// Guard pages must be consecutive for non-edge allocations
if (addr > this->addr + 4096 && g_pages[addr / 4096 - 2])
{
return false;
}
// Same
if (size + addr < this->addr + this->size - 4096 && g_pages[addr / 4096 + size / 4096 + 1])
{
return false;
}
}
// Check if memory area is already mapped
for (u32 i = addr / 4096; i <= (addr + size - 1) / 4096; i++)
{
@@ -1194,9 +1227,6 @@ namespace vm
}
}
const u32 page_addr = addr + (this->flags & stack_guarded ? 0x1000 : 0);
const u32 page_size = size - (this->flags & stack_guarded ? 0x2000 : 0);
// No flags are default to readable/writable
// Explicit (un...) flags are used to protect from such access
u8 flags = 0;
@@ -1225,15 +1255,8 @@ namespace vm
flags |= page_1m_size;
}
if (this->flags & stack_guarded)
{
// Mark overflow/underflow guard pages as allocated
ensure(!g_pages[addr / 4096].exchange(page_allocated));
ensure(!g_pages[addr / 4096 + size / 4096 - 1].exchange(page_allocated));
}
// Map "real" memory pages; provide a function to search for mirrors with private member access
_page_map(page_addr, flags, page_size, shm.get(), this->flags, [](vm::block_t* _this, utils::shm* shm)
_page_map(addr, flags, size, shm.get(), this->flags, [](vm::block_t* _this, utils::shm* shm)
{
auto& map = (_this->m.*block_map)();
@@ -1260,23 +1283,15 @@ namespace vm
// Fill stack guards with STACKGRD
if (this->flags & stack_guarded)
{
auto fill64 = [](u8* ptr, u64 data, usz count)
const auto fill64 = [](u8* ptr, u64 data, usz count)
{
#if defined(_M_X64) && defined(_MSC_VER)
__stosq(reinterpret_cast<u64*>(ptr), data, count);
#elif defined(ARCH_X64)
__asm__ ("mov %0, %%rdi; mov %1, %%rax; mov %2, %%rcx; rep stosq;"
:
: "r" (ptr), "r" (data), "r" (count)
: "rdi", "rax", "rcx", "memory");
#else
for (usz i = 0; i < count; i++)
reinterpret_cast<u64*>(ptr)[i] = data;
#endif
u64* dst = reinterpret_cast<u64*>(ptr);
std::fill(dst, dst + count, data);
};
const u32 enda = addr + size - 4096;
fill64(g_sudo_addr + addr, "STACKGRD"_u64, 4096 / sizeof(u64));
const u32 begina = addr - 4096;
const u32 enda = addr + size;
fill64(g_sudo_addr + begina, "STACKGRD"_u64, 4096 / sizeof(u64));
fill64(g_sudo_addr + enda, "UNDERFLO"_u64, 4096 / sizeof(u64));
}
@@ -1455,7 +1470,7 @@ namespace vm
const u32 min_page_size = flags & block_size_4k ? 0x1000 : 0x10000;
// Align to minimal page size
const u32 size = utils::align(orig_size, min_page_size) + (flags & stack_guarded ? 0x2000 : 0);
const u32 size = utils::align(orig_size, min_page_size);
// Check alignment (it's page allocation, so passing small values there is just silly)
if (align < min_page_size || align != (0x80000000u >> std::countl_zero(align)))
@@ -1503,7 +1518,7 @@ namespace vm
{
if (try_alloc(addr, flags, size, std::move(shm)))
{
return addr + (flags & stack_guarded ? 0x1000 : 0);
return addr;
}
if (addr == max)
@@ -1598,7 +1613,7 @@ namespace vm
vm::writer_lock lock;
const auto found = m_map.find(addr - (flags & stack_guarded ? 0x1000 : 0));
const auto found = m_map.find(addr);
if (found == m_map.end())
{
@@ -1611,14 +1626,7 @@ namespace vm
}
// Get allocation size
const auto size = found->second.first - (flags & stack_guarded ? 0x2000 : 0);
if (flags & stack_guarded)
{
// Clear guard pages
ensure(g_pages[addr / 4096 - 1].exchange(0) == page_allocated);
ensure(g_pages[addr / 4096 + size / 4096].exchange(0) == page_allocated);
}
const auto size = found->second.first;
// Unmap "real" memory pages
ensure(size == _page_unmap(addr, size, this->flags, found->second.second.get(), unmap_notification.event_data));
@@ -1684,7 +1692,7 @@ namespace vm
for (auto& entry : (m.*block_map)())
{
result += entry.second.first - (flags & stack_guarded ? 0x2000 : 0);
result += entry.second.first;
}
return result;
@@ -1834,7 +1842,7 @@ namespace vm
for (const auto& [addr, shm] : m_map)
{
// Assume first page flags represent all the map
ar(g_pages[addr / 4096 + !!(flags & stack_guarded)]);
ar(g_pages[addr / 4096]);
ar(addr);
ar(shm.first);
@@ -1851,8 +1859,7 @@ namespace vm
}
// Save raw binary image
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr + guard_size), shm.first - guard_size * 2);
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr), shm.first);
}
else
{
@@ -1918,6 +1925,10 @@ namespace vm
{
pflags |= block_size_1m;
}
else
{
pflags |= block_size_4k;
}
// Map the memory through the same method as alloc() and falloc()
// Copy the shared handle unconditionally
@@ -1926,8 +1937,7 @@ namespace vm
if (flags & preallocated)
{
// Load binary image
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr0 + guard_size), size0 - guard_size * 2);
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr0), size0);
}
}
}
+19
View File
@@ -11,6 +11,12 @@
#ifndef _WIN32
#include <unistd.h>
#include <fcntl.h>
#else
#include <MSWSock.h>
#ifndef SIO_UDP_CONNRESET
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
#endif
#endif
LOG_CHANNEL(IPv6_log, "IPv6_layer");
@@ -294,6 +300,19 @@ namespace np
::ioctlsocket(socket, FIONBIO, &_true);
#else
::fcntl(socket, F_SETFL, ::fcntl(socket, F_GETFL, 0) | O_NONBLOCK);
#endif
}
}
void set_dgram_socket_disable_connreset(socket_type socket)
{
if (socket)
{
#ifdef _WIN32
DWORD enable = FALSE;
DWORD bytes_returned = 0;
WSAIoctl(socket, SIO_UDP_CONNRESET, &enable, sizeof(enable), nullptr, 0, &bytes_returned, nullptr, nullptr);
#endif
}
}
+1
View File
@@ -82,4 +82,5 @@ namespace np
void close_socket(socket_type socket);
void set_socket_non_blocking(socket_type socket);
void set_dgram_socket_disable_connreset(socket_type socket);
} // namespace np
+24
View File
@@ -229,6 +229,30 @@ namespace np
return;
}
const u32 cast_type = message_info->casttype().value();
const int dst_size = message_info->dst_size();
bool valid_destination = false;
switch (cast_type)
{
case SCE_NP_MATCHING2_CASTTYPE_BROADCAST:
valid_destination = dst_size == 0;
break;
case SCE_NP_MATCHING2_CASTTYPE_UNICAST:
case SCE_NP_MATCHING2_CASTTYPE_MULTICAST_TEAM:
valid_destination = dst_size == 1;
break;
case SCE_NP_MATCHING2_CASTTYPE_MULTICAST:
valid_destination = dst_size > 0 && dst_size <= SCE_NP_MATCHING2_ROOM_MAX_SLOT;
break;
}
if (!valid_destination || message_info->msg().size() > SCE_NP_MATCHING2_BIN_MSG_MAX_SIZE)
{
rpcn_log.error("Received malformed RoomMessageReceived notification");
return;
}
const u32 event_key = get_event_key();
auto [include_onlinename, include_avatarurl] = get_match2_context_options(room_event_cb_ctx);
+3 -1
View File
@@ -1635,6 +1635,7 @@ namespace np
cur_status->hasData = 1;
cur_status->lastChangedDate.tick = cur_pb_status.lastchangeddate();
string_to_npid(cur_pb_status.lastchangedauthorid(), cur_status->lastChangedAuthorId);
ensure(cur_pb_status.info().size() <= SCE_NP_TUS_DATA_INFO_MAX_SIZE);
cur_status->info.infoSize = ::narrow<u32>(cur_pb_status.info().size());
memcpy(cur_status->info.data, cur_pb_status.info().data(), cur_pb_status.info().size());
}
@@ -1872,8 +1873,9 @@ namespace np
string_to_npid(pb_status.lastchangedauthorid(), data_status->lastChangedAuthorId);
data_status->data = tdata->data;
data_status->dataSize = ::narrow<u32>(pb_data->data().size());
ensure(pb_status.info().size() <= SCE_NP_TUS_DATA_INFO_MAX_SIZE);
data_status->info.infoSize = ::narrow<u32>(pb_status.info().size());
memcpy(data_status->info.data, pb_data->status().info().data(), std::min(pb_data->status().info().size(), sizeof(data_status->info.data)));
memcpy(data_status->info.data, pb_status.info().data(), pb_status.info().size());
const u32 to_copy = std::min<u32>(data_status->dataSize, tdata->recvSize);
memcpy(data, pb_data->data().data(), to_copy);
+11 -2
View File
@@ -3249,10 +3249,19 @@ namespace rpcn
return;
}
const u32 main_type = pb_mdata->maintype().value();
const u32 sub_type = pb_mdata->subtype().value();
if (main_type > std::numeric_limits<u16>::max() || sub_type > std::numeric_limits<u16>::max())
{
rpcn_log.warning("Discarded message with invalid type! (main_type=%u, sub_type=%u)", main_type, sub_type);
return;
}
message_data mdata = {
.msgId = message_counter,
.mainType = ::narrow<u16>(pb_mdata->maintype().value()),
.subType = ::narrow<u16>(pb_mdata->subtype().value()),
.mainType = static_cast<u16>(main_type),
.subType = static_cast<u16>(sub_type),
.msgFeatures = pb_mdata->msgfeatures(),
.subject = pb_mdata->subject(),
.body = pb_mdata->body()};
+21 -10
View File
@@ -280,8 +280,6 @@ void signaling_handler::process_incoming_messages()
}
const auto now = steady_clock::now();
if (si)
si->time_last_msg_recvd = now;
const auto setup_ping = [&]()
{
@@ -299,10 +297,16 @@ void signaling_handler::process_incoming_messages()
queue_signaling_packet(sent_packet, si, now + REPEAT_PING_DELAY);
};
const auto update_rtt = [&](u64 rtt_timestamp)
const auto update_rtt = [&](u64 rtt_timestamp) -> bool
{
u64 timestamp_now = get_micro_timestamp(now);
u64 rtt = timestamp_now - rtt_timestamp;
const u64 timestamp_now = get_micro_timestamp(now);
if (rtt_timestamp > timestamp_now || timestamp_now - rtt_timestamp > std::numeric_limits<u32>::max())
{
sign_log.error("Received a signaling packet with an invalid RTT timestamp");
return false;
}
const u64 rtt = timestamp_now - rtt_timestamp;
si->last_rtts[(si->rtt_counters % 6)] = rtt;
si->rtt_counters++;
@@ -313,7 +317,8 @@ void signaling_handler::process_incoming_messages()
sum += si->last_rtts[index];
}
si->rtt = ::narrow<u32>(sum / num_rtts);
si->rtt = static_cast<u32>(sum / num_rtts);
return true;
};
switch (sp->command)
@@ -325,7 +330,8 @@ void signaling_handler::process_incoming_messages()
sent_packet.timestamp_sender = sp->timestamp_sender;
break;
case signal_pong:
update_rtt(sp->timestamp_sender);
if (!update_rtt(sp->timestamp_sender))
continue;
reply = false;
schedule_repeat = false;
reschedule_packet(si, signal_ping, now + 10s);
@@ -344,7 +350,8 @@ void signaling_handler::process_incoming_messages()
update_si_addr(si, op_addr, op_port);
break;
case signal_connect_ack:
update_rtt(sp->timestamp_sender);
if (!update_rtt(sp->timestamp_sender))
continue;
reply = true;
schedule_repeat = false;
setup_ping();
@@ -356,7 +363,8 @@ void signaling_handler::process_incoming_messages()
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, CELL_OK);
break;
case signal_confirm:
update_rtt(sp->timestamp_receiver);
if (!update_rtt(sp->timestamp_receiver))
continue;
reply = false;
schedule_repeat = false;
setup_ping();
@@ -381,6 +389,9 @@ void signaling_handler::process_incoming_messages()
default: sign_log.error("Invalid signaling command received"); continue;
}
if (si)
si->time_last_msg_recvd = now;
if (reply)
{
send_signaling_packet(sent_packet, op_addr, op_port);
@@ -427,7 +438,7 @@ void signaling_handler::operator()()
auto retire_info = sig.sig_info;
sign_log.notice("Timeout disconnection");
update_si_status(retire_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT);
retire_packet(retire_info, signal_ping); // Retire ping packet if necessary
retire_all_packets(retire_info); // A connect_ack and/or ping may still be in queue
break; // qpackets has been emptied of all packets for this user so we're requeuing
}
+208 -23
View File
@@ -8,6 +8,8 @@
#if defined(ARCH_X64)
#include "BufferUtils_avx512.h"
#elif defined(ARCH_ARM64)
#include "BufferUtils_neon.h"
#endif
#if !defined(_MSC_VER)
@@ -21,6 +23,9 @@
#endif
#undef FORCE_INLINE
#include "Emu/CPU/sse2neon.h"
#if defined(__GNUC__) || defined(__clang__)
#include "BufferUtils_sve.h"
#endif
#endif
#if defined(_MSC_VER) || !defined(__SSE2__)
@@ -68,6 +73,10 @@
const v128 s_bswap_u32_mask = v128::from32(0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f);
const v128 s_bswap_u16_mask = v128::from32(0x02030001, 0x06070405, 0x0a0b0809, 0x0e0f0c0d);
#if defined(ARCH_X64) || defined(ARCH_ARM64)
#include "BufferUtils_compact.h"
#endif
namespace utils
{
template <typename T, typename U>
@@ -183,6 +192,9 @@ namespace
#if defined(ARCH_X64)
DECLARE(copy_data_swap_u32) = build_function_asm<void(*)(u32*, const u32*, u32), asmjit::simd_builder>("copy_data_swap_u32", &build_copy_data_swap_u32<false>);
DECLARE(copy_data_swap_u32_cmp) = build_function_asm<bool(*)(u32*, const u32*, u32), asmjit::simd_builder>("copy_data_swap_u32_cmp", &build_copy_data_swap_u32<true>);
#elif defined(ARCH_ARM64)
DECLARE(copy_data_swap_u32) = copy_data_swap_u32_neon<false>;
DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_neon<true>;
#else
DECLARE(copy_data_swap_u32) = copy_data_swap_u32_naive<false>;
DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_naive<true>;
@@ -299,6 +311,8 @@ namespace
r = upload_xi16(src.data(), dst.data(), count);
else
r = upload_xi32(src.data(), dst.data(), count);
#elif defined(ARCH_ARM64)
r = upload_untouched_neon<T, false>(src.data(), dst.data(), count);
#else
r = upload_untouched_naive(src.data(), dst.data(), count);
#endif
@@ -405,6 +419,8 @@ namespace
r = upload_xi16(src.data(), dst.data(), count, restart_index);
else
r = upload_xi32(src.data(), dst.data(), count, restart_index);
#elif defined(ARCH_ARM64)
r = upload_untouched_neon<T, true>(src.data(), dst.data(), count, restart_index);
#else
r = upload_untouched_naive(src.data(), dst.data(), count, restart_index);
#endif
@@ -445,7 +461,7 @@ namespace
return std::make_tuple(min_index, max_index, written);
}
const upload_untouched_skip_restart_dispatch s_generic_upload_untouched_skip_restart_dispatch =
[[maybe_unused]] const upload_untouched_skip_restart_dispatch s_generic_upload_untouched_skip_restart_dispatch =
{
upload_untouched_skip_restart<u16>,
upload_untouched_skip_restart<u32>,
@@ -454,7 +470,7 @@ namespace
#if defined(ARCH_X64)
const upload_untouched_skip_restart_dispatch s_avx512_upload_untouched_skip_restart_dispatch =
{
upload_untouched_skip_restart<u16>,
upload_swapped_avx2_skip_restart<u16>,
upload_u32_swapped_avx3_skip_restart,
};
@@ -477,8 +493,41 @@ namespace
{
return s_avx512_upload_untouched_skip_restart_dispatch;
}
if (s_use_avx2)
{
static const upload_untouched_skip_restart_dispatch s_avx2 =
{
upload_swapped_avx2_skip_restart<u16>,
upload_swapped_avx2_skip_restart<u32>,
};
return s_avx2;
}
#elif defined(ARCH_ARM64) && (defined(__GNUC__) || defined(__clang__))
if (utils::has_sve())
{
static const upload_untouched_skip_restart_dispatch s_sve =
{
#ifdef BUFFERUTILS_HAS_SVE2P2
utils::has_sve2p2() ? upload_swapped_sve2p2_skip_restart : upload_swapped_neon_skip_restart<u16>,
#else
upload_swapped_neon_skip_restart<u16>,
#endif
upload_swapped_sve_skip_restart,
};
return s_sve;
}
#endif
#if defined(ARCH_ARM64)
static const upload_untouched_skip_restart_dispatch s_neon =
{
upload_swapped_neon_skip_restart<u16>,
upload_swapped_neon_skip_restart<u32>,
};
return s_neon;
#else
return s_generic_upload_untouched_skip_restart_dispatch;
#endif
}();
template<typename T, typename U = remove_be_t<T>>
@@ -514,25 +563,6 @@ namespace
return primitive_restart_impl::upload_untouched(src, dst, static_cast<U>(primitive_restart_index));
}
void iota16(u16* dst, u32 count)
{
unsigned i = 0;
#if defined(ARCH_X64) || defined(ARCH_ARM64)
const unsigned step = 8; // We do 8 entries per step
const __m128i vec_step = _mm_set1_epi16(8); // Constant to increment the raw values
__m128i values = _mm_set_epi16(7, 6, 5, 4, 3, 2, 1, 0);
__m128i* vec_ptr = utils::bless<__m128i>(dst);
for (; (i + step) <= count; i += step, vec_ptr++)
{
_mm_stream_si128(vec_ptr, values);
values = _mm_add_epi16(values, vec_step);
}
#endif
for (; i < count; ++i)
dst[i] = i;
}
template<typename T>
std::tuple<T, T, u32> expand_indexed_triangle_fan(std::span<to_be_t<const T>> src, std::span<T> dst, bool is_primitive_restart_enabled, u32 primitive_restart_index)
{
@@ -627,6 +657,161 @@ namespace
}
}
#if defined(ARCH_X64)
namespace
{
AVX2_FUNC void iota16_avx2(u16* dst, u32 count)
{
u32 i = 0;
// Force 32-byte alignment
const uptr mem_addr = reinterpret_cast<uptr>(dst);
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 31) / sizeof(u16));
for (; i < head; ++i)
{
dst[i] = i;
}
constexpr u32 step = 32; // We do 32 entries per step
const __m256i vec_step = _mm256_set1_epi16(32); // Constant to increment the raw values
__m256i values0 = _mm256_add_epi16(_mm256_set_epi16(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), _mm256_set1_epi16(static_cast<s16>(i)));
__m256i values1 = _mm256_add_epi16(values0, _mm256_set1_epi16(16));
__m256i* vec_ptr = utils::bless<__m256i>(dst + i);
for (; (i + step) <= count; i += step, vec_ptr += 2)
{
_mm256_store_si256(vec_ptr, values0);
_mm256_store_si256(vec_ptr + 1, values1);
values0 = _mm256_add_epi16(values0, vec_step);
values1 = _mm256_add_epi16(values1, vec_step);
}
// Half-line remainder
if ((i + step / 2) <= count)
{
_mm256_store_si256(vec_ptr, values0);
i += step / 2;
}
_mm256_zeroupper();
for (; i < count; ++i)
dst[i] = i;
}
AVX2_FUNC void iota32_avx2(u32* dst, u32 count)
{
u32 i = 0;
// Force 32-byte alignment
const uptr mem_addr = reinterpret_cast<uptr>(dst);
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 31) / sizeof(u32));
for (; i < head; ++i)
{
dst[i] = i;
}
constexpr u32 step = 16; // We do 16 entries per step
const __m256i vec_step = _mm256_set1_epi32(16); // Constant to increment the raw values
__m256i values0 = _mm256_add_epi32(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0), _mm256_set1_epi32(i));
__m256i values1 = _mm256_add_epi32(values0, _mm256_set1_epi32(8));
__m256i* vec_ptr = utils::bless<__m256i>(dst + i);
for (; (i + step) <= count; i += step, vec_ptr += 2)
{
_mm256_store_si256(vec_ptr, values0);
_mm256_store_si256(vec_ptr + 1, values1);
values0 = _mm256_add_epi32(values0, vec_step);
values1 = _mm256_add_epi32(values1, vec_step);
}
// Half-line remainder
if ((i + step / 2) <= count)
{
_mm256_store_si256(vec_ptr, values0);
i += step / 2;
}
_mm256_zeroupper();
for (; i < count; ++i)
dst[i] = i;
}
}
#endif
void iota16(u16* dst, u32 count)
{
#if defined(ARCH_X64)
if (s_use_avx2)
{
iota16_avx2(dst, count);
return;
}
#endif
u32 i = 0;
#if defined(ARCH_X64) || defined(ARCH_ARM64)
// Force 16-byte alignment
const uptr mem_addr = reinterpret_cast<uptr>(dst);
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 15) / sizeof(u16));
for (; i < head; ++i)
{
dst[i] = i;
}
constexpr u32 step = 8; // We do 8 entries per step
const __m128i vec_step = _mm_set1_epi16(8); // Constant to increment the raw values
__m128i values = _mm_set_epi16(7, 6, 5, 4, 3, 2, 1, 0);
__m128i* vec_ptr = utils::bless<__m128i>(dst + i);
values = _mm_add_epi16(values, _mm_set1_epi16(static_cast<s16>(i)));
for (; (i + step) <= count; i += step, vec_ptr++)
{
_mm_store_si128(vec_ptr, values);
values = _mm_add_epi16(values, vec_step);
}
#endif
for (; i < count; ++i)
dst[i] = i;
}
void iota32(u32* dst, u32 count)
{
#if defined(ARCH_X64)
if (s_use_avx2)
{
iota32_avx2(dst, count);
return;
}
#endif
u32 i = 0;
#if defined(ARCH_X64) || defined(ARCH_ARM64)
// Force 16-byte alignment
const uptr mem_addr = reinterpret_cast<uptr>(dst);
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 15) / sizeof(u32));
for (; i < head; ++i)
{
dst[i] = i;
}
constexpr u32 step = 4; // We do 4 entries per step
const __m128i vec_step = _mm_set1_epi32(4); // Constant to increment the raw values
__m128i values = _mm_set_epi32(3, 2, 1, 0);
__m128i* vec_ptr = utils::bless<__m128i>(dst + i);
values = _mm_add_epi32(values, _mm_set1_epi32(i));
for (; (i + step) <= count; i += step, vec_ptr++)
{
_mm_store_si128(vec_ptr, values);
values = _mm_add_epi32(values, vec_step);
}
#endif
for (; i < count; ++i)
dst[i] = i;
}
// Only handle quads and triangle fan now
bool is_primitive_native(rsx::primitive_type draw_mode)
{
@@ -697,11 +882,11 @@ u32 get_index_type_size(rsx::index_array_type type)
void write_index_array_for_non_indexed_non_native_primitive_to_buffer(char* dst, rsx::primitive_type draw_mode, unsigned count)
{
auto typedDst = reinterpret_cast<u16*>(dst);
auto typedDst = reinterpret_cast<u32*>(dst);
switch (draw_mode)
{
case rsx::primitive_type::line_loop:
iota16(typedDst, count);
iota32(typedDst, count);
typedDst[count] = 0;
return;
case rsx::primitive_type::triangle_fan:
+4
View File
@@ -43,3 +43,7 @@ extern void(*const copy_data_swap_u32)(u32* dst, const u32* src, u32 count);
// Copy and swap data in 32-bit units, return true if changed
extern bool(*const copy_data_swap_u32_cmp)(u32* dst, const u32* src, u32 count);
void iota16(u16* dst, u32 count);
void iota32(u32* dst, u32 count);
+202
View File
@@ -0,0 +1,202 @@
#pragma once
namespace
{
template <usz Bytes, usz Lanes>
constexpr auto make_compress_table()
{
std::array<std::array<u8, 16>, 1u << Lanes> table{};
for (usz mask = 0; mask < table.size(); ++mask)
{
table[mask].fill(0xff);
usz out = 0;
for (usz lane = 0; lane < Lanes; ++lane)
{
if (mask & (1u << lane))
{
for (usz byte = 0; byte < Bytes; ++byte)
{
table[mask][out++] = static_cast<u8>(lane * Bytes + byte);
}
}
}
}
return table;
}
alignas(64) constexpr auto s_compress_u16 = make_compress_table<2, 8>();
alignas(64) constexpr auto s_compress_u32 = make_compress_table<4, 4>();
#if defined(ARCH_ARM64)
template <typename T>
std::tuple<T, T, u32> upload_swapped_neon_skip_restart(std::span<to_be_t<const T>> src, std::span<T> dst, T restart_index)
{
const u32 count = ::size32(src);
u32 i = 0;
u32 written = 0;
T min_index = static_cast<T>(-1);
T max_index = 0;
if constexpr (sizeof(T) == 2)
{
auto min = vdupq_n_u16(-1);
auto max = vdupq_n_u16(0);
const u16 weights[] = {1, 2, 4, 8, 16, 32, 64, 128};
for (; count - i >= 8; i += 8)
{
const auto value = vreinterpretq_u16_u8(vrev16q_u8(vld1q_u8(reinterpret_cast<const u8*>(src.data() + i))));
const auto removed = vceqq_u16(value, vdupq_n_u16(restart_index));
min = vminq_u16(min, vorrq_u16(value, removed));
max = vmaxq_u16(max, vbicq_u16(value, removed));
const u32 mask = vaddvq_u16(vbicq_u16(vld1q_u16(weights), removed));
const auto packed = vqtbl1q_u8(vreinterpretq_u8_u16(value), vld1q_u8(s_compress_u16[mask].data()));
// written <= i, so the full store stays inside the input-sized destination.
vst1q_u8(reinterpret_cast<u8*>(dst.data() + written), packed);
written += std::popcount(mask);
}
min_index = vminvq_u16(min);
max_index = vmaxvq_u16(max);
}
else
{
auto min = vdupq_n_u32(-1);
auto max = vdupq_n_u32(0);
const u32 weights[] = {1, 2, 4, 8};
for (; count - i >= 4; i += 4)
{
const auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src.data() + i))));
const auto removed = vceqq_u32(value, vdupq_n_u32(restart_index));
min = vminq_u32(min, vorrq_u32(value, removed));
max = vmaxq_u32(max, vbicq_u32(value, removed));
const u32 mask = vaddvq_u32(vbicq_u32(vld1q_u32(weights), removed));
const auto packed = vqtbl1q_u8(vreinterpretq_u8_u32(value), vld1q_u8(s_compress_u32[mask].data()));
vst1q_u8(reinterpret_cast<u8*>(dst.data() + written), packed);
written += std::popcount(mask);
}
min_index = vminvq_u32(min);
max_index = vmaxvq_u32(max);
}
for (; i < count; ++i)
{
const T value = src[i];
if (value != restart_index)
{
min_index = std::min(min_index, value);
max_index = std::max(max_index, value);
dst[written++] = value;
}
}
return {min_index, max_index, written};
}
#elif defined(ARCH_X64)
alignas(64) constexpr auto s_compress_u32_avx2 = []
{
std::array<std::array<u32, 8>, 256> table{};
for (u32 mask = 0; mask < 256; ++mask)
{
u32 out = 0;
for (u32 lane = 0; lane < 8; ++lane)
{
if (mask & (1u << lane))
{
table[mask][out++] = lane;
}
}
}
return table;
}();
template <typename T>
AVX2_FUNC std::tuple<T, T, u32> upload_swapped_avx2_skip_restart(std::span<to_be_t<const T>> src, std::span<T> dst, T restart_index)
{
const u32 count = ::size32(src);
u32 i = 0;
u32 written = 0;
auto min = _mm256_set1_epi32(-1);
auto max = _mm256_setzero_si256();
const auto swap = _mm256_broadcastsi128_si256(sizeof(T) == 2 ? s_bswap_u16_mask : s_bswap_u32_mask);
const auto restart = sizeof(T) == 2 ? _mm256_set1_epi16(restart_index) : _mm256_set1_epi32(restart_index);
constexpr u32 lanes = 32 / sizeof(T);
for (; count - i >= lanes; i += lanes)
{
const auto raw = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src.data() + i));
const auto value = _mm256_shuffle_epi8(raw, swap);
if constexpr (sizeof(T) == 2)
{
const auto removed = _mm256_cmpeq_epi16(value, restart);
min = _mm256_min_epu16(min, _mm256_or_si256(value, removed));
max = _mm256_max_epu16(max, _mm256_andnot_si256(removed, value));
const auto lo_mask = _mm256_castsi256_si128(removed);
const auto hi_mask = _mm256_extracti128_si256(removed, 1);
const u32 mask = static_cast<u32>(~_mm_movemask_epi8(_mm_packs_epi16(lo_mask, hi_mask))) & 0xffff;
const u32 lo = mask & 255;
const u32 hi = mask >> 8;
const auto a = _mm_shuffle_epi8(_mm256_castsi256_si128(value), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s_compress_u16[lo].data())));
const auto b = _mm_shuffle_epi8(_mm256_extracti128_si256(value, 1), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s_compress_u16[hi].data())));
_mm_storeu_si128(utils::bless<__m128i>(dst.data() + written), a);
written += std::popcount(lo);
_mm_storeu_si128(utils::bless<__m128i>(dst.data() + written), b);
written += std::popcount(hi);
}
else
{
const auto removed = _mm256_cmpeq_epi32(value, restart);
min = _mm256_min_epu32(min, _mm256_or_si256(value, removed));
max = _mm256_max_epu32(max, _mm256_andnot_si256(removed, value));
const u32 mask = static_cast<u32>(~_mm256_movemask_ps(_mm256_castsi256_ps(removed))) & 255;
const auto indices = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(s_compress_u32_avx2[mask].data()));
const auto packed = _mm256_permutevar8x32_epi32(value, indices);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dst.data() + written), packed);
written += std::popcount(mask);
}
}
T min_index, max_index;
if constexpr (sizeof(T) == 2)
{
const auto min128 = _mm_min_epu16(_mm256_castsi256_si128(min), _mm256_extracti128_si256(min, 1));
const auto max128 = _mm_max_epu16(_mm256_castsi256_si128(max), _mm256_extracti128_si256(max, 1));
min_index = static_cast<T>(_mm_cvtsi128_si32(_mm_minpos_epu16(min128)));
max_index = static_cast<T>(~_mm_cvtsi128_si32(_mm_minpos_epu16(_mm_xor_si128(max128, _mm_set1_epi32(-1)))));
}
else
{
auto min128 = _mm_min_epu32(_mm256_castsi256_si128(min), _mm256_extracti128_si256(min, 1));
auto max128 = _mm_max_epu32(_mm256_castsi256_si128(max), _mm256_extracti128_si256(max, 1));
min128 = _mm_min_epu32(min128, _mm_shuffle_epi32(min128, _MM_SHUFFLE(1, 0, 3, 2)));
max128 = _mm_max_epu32(max128, _mm_shuffle_epi32(max128, _MM_SHUFFLE(1, 0, 3, 2)));
min128 = _mm_min_epu32(min128, _mm_shuffle_epi32(min128, _MM_SHUFFLE(2, 3, 0, 1)));
max128 = _mm_max_epu32(max128, _mm_shuffle_epi32(max128, _MM_SHUFFLE(2, 3, 0, 1)));
min_index = static_cast<T>(_mm_cvtsi128_si32(min128));
max_index = static_cast<T>(_mm_cvtsi128_si32(max128));
}
for (; i < count; ++i)
{
const T value = src[i];
if (value != restart_index)
{
min_index = std::min(min_index, value);
max_index = std::max(max_index, value);
dst[written++] = value;
}
}
return {min_index, max_index, written};
}
#endif
} // namespace
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include <arm_neon.h>
namespace
{
template <bool Compare>
auto copy_data_swap_u32_neon(u32* dst, const u32* src, u32 count)
{
uint32x4_t changed = vdupq_n_u32(0);
u32 tail_changed = 0;
u32 i = 0;
for (; count - i >= 4; i += 4)
{
const auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
if constexpr (Compare)
{
changed = vorrq_u32(changed, veorq_u32(value, vld1q_u32(dst + i)));
}
vst1q_u32(dst + i, value);
}
for (; i < count; ++i)
{
const u32 value = stx::se_storage<u32>::swap(src[i]);
if constexpr (Compare)
{
tail_changed |= value ^ dst[i];
}
dst[i] = value;
}
if constexpr (Compare)
{
return (vmaxvq_u32(changed) | tail_changed) != 0;
}
}
template <typename T, bool Restart>
u64 upload_untouched_neon(const be_t<T>* src, T* dst, u32 count, T restart_index = 0)
{
T min_index = static_cast<T>(-1);
T max_index = 0;
u32 i = 0;
if constexpr (sizeof(T) == 2)
{
auto min = vdupq_n_u16(-1);
auto max = vdupq_n_u16(0);
const auto restart = vdupq_n_u16(restart_index);
for (; count - i >= 8; i += 8)
{
auto value = vreinterpretq_u16_u8(vrev16q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
if constexpr (Restart)
{
const auto mask = vceqq_u16(value, restart);
max = vmaxq_u16(max, vbicq_u16(value, mask));
value = vorrq_u16(value, mask);
}
else
{
max = vmaxq_u16(max, value);
}
min = vminq_u16(min, value);
vst1q_u16(dst + i, value);
}
min_index = vminvq_u16(min);
max_index = vmaxvq_u16(max);
}
else
{
auto min = vdupq_n_u32(-1);
auto max = vdupq_n_u32(0);
const auto restart = vdupq_n_u32(restart_index);
for (; count - i >= 4; i += 4)
{
auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
if constexpr (Restart)
{
const auto mask = vceqq_u32(value, restart);
max = vmaxq_u32(max, vbicq_u32(value, mask));
value = vorrq_u32(value, mask);
}
else
{
max = vmaxq_u32(max, value);
}
min = vminq_u32(min, value);
vst1q_u32(dst + i, value);
}
min_index = vminvq_u32(min);
max_index = vmaxvq_u32(max);
}
for (; i < count; ++i)
{
const T value = src[i];
if (Restart && value == restart_index)
{
dst[i] = static_cast<T>(-1);
}
else
{
min_index = std::min(min_index, value);
max_index = std::max(max_index, value);
dst[i] = value;
}
}
return (u64{max_index} << 32) | min_index;
}
} // namespace
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include <arm_sve.h>
#if defined(__clang__)
#define BUFFERUTILS_SVE __attribute__((target("sve")))
#else
#define BUFFERUTILS_SVE __attribute__((target("+sve")))
#endif
// Older compilers do not provide the svcompact_u16 intrinsic.
// Please remove this nonsense once we upgrade gcc in the CI - Whatcookie
#if (defined(__clang__) && __clang_major__ >= 22) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 17)
#define BUFFERUTILS_HAS_SVE2P2
#if defined(__clang__)
#define BUFFERUTILS_SVE2P2 __attribute__((target("sve2p2")))
#else
#define BUFFERUTILS_SVE2P2 __attribute__((target("+sve2p2")))
#endif
#endif
namespace
{
BUFFERUTILS_SVE std::tuple<u32, u32, u32> upload_swapped_sve_skip_restart(std::span<to_be_t<const u32>> src, std::span<u32> dst, u32 restart_index)
{
const u32 count = ::size32(src);
u32 written = 0;
auto min = svdup_n_u32(u32{0xffffffff});
auto max = svdup_n_u32(0);
const auto all = svptrue_b32();
for (u64 i = 0; i < count; i += svcntw())
{
const auto active = svwhilelt_b32(i, u64{count});
auto value = svld1_u32(active, reinterpret_cast<const u32*>(src.data()) + i);
value = svrevb_u32_x(active, value);
const auto keep = svcmpne_n_u32(active, value, restart_index);
min = svmin_u32_m(keep, min, value);
max = svmax_u32_m(keep, max, value);
const auto packed = svcompact_u32(keep, value);
const u32 processed = svcntp_b32(all, keep);
const auto output = svwhilelt_b32(u64{0}, u64{processed});
svst1_u32(output, dst.data() + written, packed);
written += processed;
}
return {svminv_u32(all, min), svmaxv_u32(all, max), written};
}
#ifdef BUFFERUTILS_HAS_SVE2P2
BUFFERUTILS_SVE2P2 std::tuple<u16, u16, u32> upload_swapped_sve2p2_skip_restart(std::span<to_be_t<const u16>> src, std::span<u16> dst, u16 restart_index)
{
const u32 count = ::size32(src);
u32 written = 0;
auto min = svdup_n_u16(u16{0xffff});
auto max = svdup_n_u16(0);
const auto all = svptrue_b16();
for (u64 i = 0; i < count; i += svcnth())
{
const auto active = svwhilelt_b16(i, u64{count});
auto value = svld1_u16(active, reinterpret_cast<const u16*>(src.data()) + i);
value = svrevb_u16_x(active, value);
const auto keep = svcmpne_n_u16(active, value, restart_index);
min = svmin_u16_m(keep, min, value);
max = svmax_u16_m(keep, max, value);
const auto packed = svcompact_u16(keep, value);
const u32 processed = svcntp_b16(all, keep);
const auto output = svwhilelt_b16(u64{0}, u64{processed});
svst1_u16(output, dst.data() + written, packed);
written += processed;
}
return {svminv_u16(all, min), svmaxv_u16(all, max), written};
}
#endif
} // namespace
#undef BUFFERUTILS_SVE
#ifdef BUFFERUTILS_HAS_SVE2P2
#undef BUFFERUTILS_SVE2P2
#endif
+56 -2
View File
@@ -1502,6 +1502,24 @@ namespace rsx
}
}
bool is_float_depth_format(rsx::surface_depth_format2 format)
{
switch (format)
{
case rsx::surface_depth_format2::z16_float:
case rsx::surface_depth_format2::z24s8_float:
return true;
default:
return false;
}
}
f32 encode_emulated_depth(f32 depth)
{
// Halving the IEEE bit pattern of a non-negative float is monotonic and maps every finite value below 1
return std::bit_cast<f32>((std::bit_cast<u32>(std::max(depth, 0.f)) & 0x7fffffffu) >> 1);
}
/**
* Returns number of texel lines decoded in one pitch-length number of bytes
*/
@@ -1827,9 +1845,45 @@ namespace rsx
}
}
u32 get_max_depth_value(rsx::surface_depth_format2 format)
f32 decode_e4m12(u32 value)
{
return get_format_block_size_in_bytes(format) == 2 ? 0xFFFF : 0xFFFFFF;
// Rebias by 2^111: unsigned E4M12 with bias 16, see E4M12Conversion.glsl
const uint bits = (value << 11) & 0x07FFF800u;
return std::bit_cast<f32>(bits) * std::bit_cast<f32>(0x77000000u);
}
f32 get_depth_clear_value(rsx::surface_depth_format2 format, u32 raw)
{
switch (format)
{
case rsx::surface_depth_format2::z16_uint:
return float(raw) / 0xFFFF;
case rsx::surface_depth_format2::z24s8_uint:
return float(raw) / 0xFFFFFF;
case rsx::surface_depth_format2::z16_float:
return decode_e4m12(raw);
case rsx::surface_depth_format2::z24s8_float:
return std::bit_cast<f32>(raw << 7);
default:
fmt::throw_exception("Unreachable");
}
}
// Hardware behaves as if each depth bound is converted into the surface's depth encoding, clamped to the format's range.
f32 clamp_depth_bounds_value(rsx::surface_depth_format2 format, f32 value)
{
switch (format)
{
case rsx::surface_depth_format2::z16_uint:
case rsx::surface_depth_format2::z24s8_uint:
return std::clamp(value, 0.f, 1.f);
case rsx::surface_depth_format2::z16_float:
return std::clamp(value, 0.f, decode_e4m12(0xFFFF));
case rsx::surface_depth_format2::z24s8_float:
return std::max(value, 0.f);
default:
fmt::throw_exception("Unreachable");
}
}
bool is_texcoord_wrapping_mode(rsx::texture_wrap_mode mode)
+6 -1
View File
@@ -364,8 +364,13 @@ namespace rsx
bool is_compressed_host_format(const texture_uploader_capabilities& caps, u32 format); // Returns true for host-compressed formats (DXT)
u8 get_format_sample_count(rsx::surface_antialiasing antialias);
u32 get_max_depth_value(rsx::surface_depth_format2 format);
f32 get_depth_clear_value(rsx::surface_depth_format2 format, u32 raw);
f32 clamp_depth_bounds_value(rsx::surface_depth_format2 format, f32 value);
bool is_depth_stencil_format(rsx::surface_depth_format2 format);
bool is_float_depth_format(rsx::surface_depth_format2 format);
// Storage encoding of float depth when the host cannot hold depth values above 1. Must match encode_emulated_depth in GLSL.
f32 encode_emulated_depth(f32 depth);
/**
* Format feature support. There is not simple format to determine what is supported here, results are from hw tests
+1 -1
View File
@@ -133,7 +133,7 @@ namespace rsx
m_data |= mask;
}
bool test(T mask)
bool test(T mask) const
{
return !!(m_data & static_cast<bitmask_type>(mask));
}
+33 -8
View File
@@ -1285,7 +1285,14 @@ namespace rsx
}
template <typename commandbuffer_type>
rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(commandbuffer_type& cmd, u32 texaddr, u32 required_width, u32 required_height, u32 required_pitch, u8 required_bpp, rsx::surface_access access)
rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(
commandbuffer_type& cmd,
u32 texaddr,
u32 required_width,
u32 required_height,
u32 required_pitch,
u8 required_bpp,
rsx::surface_access access)
{
rsx::simple_array<surface_overlap_info> result;
rsx::simple_array<utils::pair<u32, bool>> dirty;
@@ -1432,17 +1439,35 @@ namespace rsx
if (result.size() > 1)
{
result.sort([](const auto &a, const auto &b)
result.sort([texaddr](const auto &a, const auto &b)
{
if (a.surface->last_use_tag == b.surface->last_use_tag)
// Check for the common case first. 99.99% of lookups end here.
if (a.surface->last_use_tag != b.surface->last_use_tag) [[ likely ]]
{
const auto area_a = a.dst_area.width * a.dst_area.height;
const auto area_b = b.dst_area.width * b.dst_area.height;
return area_a < area_b;
return a.surface->last_use_tag < b.surface->last_use_tag;
}
return a.surface->last_use_tag < b.surface->last_use_tag;
// Surfaces are aliasing. We need a tie-breaker
if (a.is_depth != b.is_depth &&
g_cfg.video.fb_aliasing_bias != framebuffer_aliasing_bias::_auto)
{
// User-defined preference for mismatched depth/color aliasing. This is the most common aliasing cause.
// Preferred aspect goes last.
const bool prefer_color = g_cfg.video.fb_aliasing_bias == framebuffer_aliasing_bias::prefer_color;
return prefer_color ? a.is_depth : b.is_depth;
}
// Check if we have a perfect match and make it go last
if (a.base_address == texaddr && !a.is_clipped) return false;
if (b.base_address == texaddr && !b.is_clipped) return true;
// Check if one of the two is able to serve the full request better than the other.
if (a.is_clipped != b.is_clipped) return a.is_clipped;
// Compare available area if nothing else could break the tie
const auto area_a = a.dst_area.width * a.dst_area.height;
const auto area_b = b.dst_area.width * b.dst_area.height;
return area_a < area_b;
});
}
+9
View File
@@ -696,6 +696,15 @@ namespace rsx
rop_control.set_blend_target_mask(blend_enable_mask);
}
if (fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_RANGE)
{
// Hardware clipping is disabled for these draws, see the fragment epilogue
if (REGS(m_ctx)->depth_clamp_enabled() || !REGS(m_ctx)->depth_clip_enabled())
{
rop_control.enable_depth_clamp();
}
}
// Generate wpos coefficients
// wpos equation is now as follows (ignoring pixel center offset):
// wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0)
+6 -5
View File
@@ -36,14 +36,15 @@ namespace rsx
rtt_config_dirty = (1 << 21), // Render target configuration changed
rtt_config_contested = (1 << 22), // Render target configuration is indeterminate
rtt_config_valid = (1 << 23), // Render target configuration is valid
rtt_cache_state_dirty = (1 << 24), // Texture cache state is indeterminate
rtt_config_no_attachments = (1 << 24), // Render target configuration has no attachments
rtt_cache_state_dirty = (1 << 25), // Texture cache state is indeterminate
xform_instancing_state_dirty = (1 << 25), // Transform instancing state has changed
xform_instancing_state_dirty = (1 << 26), // Transform instancing state has changed
zeta_address_is_cyclic = (1 << 26), // The currently bound Z buffer is active for R/W in a cyclic manner
zeta_address_cyclic_barrier = (1 << 27), // A memory barrier is required to "end" the Z buffer cyclic state
zeta_address_is_cyclic = (1 << 27), // The currently bound Z buffer is active for R/W in a cyclic manner
zeta_address_cyclic_barrier = (1 << 28), // A memory barrier is required to "end" the Z buffer cyclic state
blend_config_dirty = (1 << 28), // Blending configuration has been updated. Only matters when programmable blending is in use.
blend_config_dirty = (1 << 29), // Blending configuration has been updated. Only matters when programmable blending is in use.
// TODO - Should signal that we simply need to do a FP compare before the next draw call and invalidate the ucode if the content has changed.
// Marking as dirty to invalidate hot cache also works, it's not like there's tons of barriers per frame anyway.
+2 -1
View File
@@ -20,7 +20,8 @@ namespace gl
{"tc6", 12},
{"tc7", 13},
{"tc8", 14},
{"tc9", 15}
{"tc9", 15},
{"depth_range", 16} // clip range for depth range emulation
}};
int get_varying_register_location(std::string_view varying_register_name)

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