Compare commits

...

369 Commits

Author SHA1 Message Date
Evan Husted
61303032f1 Silence compile warnings 2024-12-24 00:31:49 -06:00
Evan Husted
bff884a89c UI: Change backend text based on used Window type rather than configured value 2024-12-24 00:18:47 -06:00
Evan Husted
89f3c8235c misc: More places need to factor in Auto, oops 2024-12-23 23:53:58 -06:00
Evan Husted
f3545f5eae UI: Auto graphics backend 2024-12-23 23:26:47 -06:00
Evan Husted
a335c8ff2b UI: Collapse IsHypervisorAvailable and IsMetalAvailable into IsAppleSiliconMac 2024-12-23 22:37:15 -06:00
Evan Husted
3aaeaf3540 UI: Only allow ARM macs to select Metal backend 2024-12-23 22:35:28 -06:00
Evan Husted
e82a90993c UI: Properly disable selecting Metal on non-Mac platforms 2024-12-23 22:32:37 -06:00
Evan Husted
f3dcb80a60 GAL changes 2024-12-23 21:52:39 -06:00
Evan Husted
d4b9e06522 .NET 9 2024-12-23 21:44:45 -06:00
Isaac Marovitz
0945ea68fe Make resource encoding less stupid 2024-12-23 21:44:34 -06:00
Isaac Marovitz
d3c0971acf Set correct maximum supported anisotropy 2024-12-23 21:44:31 -06:00
Isaac Marovitz
7bdae9e552 GAL Changes 2024-12-23 21:44:29 -06:00
Isaac Marovitz
567b0a5027 Updates 2024-12-23 21:44:26 -06:00
Isaac Marovitz
a5f82a99a3 GAL Changes 2024-12-23 21:44:23 -06:00
Isaac Marovitz
89c05ac239 Check for null resources before declaring them resident 2024-12-23 21:44:17 -06:00
Isaac Marovitz
fe4c77788f Fix null sampler crash 2024-12-23 21:44:11 -06:00
Isaac Marovitz
d23de14812 Fix counted indirect draws
Fixes Monster Hunter Rise and Apollo Justice
2024-12-23 21:44:07 -06:00
Isaac Marovitz
216261931e Program hash set 2024-12-23 21:44:04 -06:00
Isaac Marovitz
8c2f3ae8d2 Auto-backed samplers 2024-12-23 21:44:02 -06:00
Isaac Marovitz
a710bcd874 Refactor binding logic + Bind image arrays 2024-12-23 21:43:49 -06:00
Isaac Marovitz
b941ef6bde Fix primitive id in shader gen
Fixes Dark Souls
2024-12-23 21:43:47 -06:00
Isaac Marovitz
b5e6f26296 Fix cubemap array length
Fixes crash in Sonic Frontiers
2024-12-23 21:43:45 -06:00
Isaac Marovitz
c0d20f8689 Properly create stencil views of combined formats
Fixes Link’s Awakening
2024-12-23 21:43:42 -06:00
Isaac Marovitz
4a81d9edc4 Add missing set texture for depth stencil blit
Mostly fixes Sonic Frontiers & Link’s Awakening
2024-12-23 21:43:38 -06:00
Isaac Marovitz
a05a9a33f1 Fix typo in stride change shader
Fixes Castlevania Dominus Collection
2024-12-23 21:43:35 -06:00
Isaac Marovitz
b1e5262893 Fix invalid depth stencil state when no depth stencil is present
Partially fixes Sonic Frontiers and Castlevania Dominus Collection
2024-12-23 21:43:32 -06:00
Isaac Marovitz
dce6b94841 Style 2024-12-23 21:43:30 -06:00
Isaac Marovitz
51e85ed38b Metal: Unsupported topology indexed draw conversion (#40)
* Convert unsupported indexed buffer topologies

* Fix index count and dispatch size

* Cleanup

* Fix typos
2024-12-23 21:43:23 -06:00
Isaac Marovitz
38b2cf9b83 Fix null resources breaking arg buffer alignment 2024-12-23 21:43:16 -06:00
Isaac Marovitz
e406b67690 Remove RenderPipelineDescriptorResult 2024-12-23 21:43:13 -06:00
Isaac Marovitz
2d522b1675 D32FS8 to D24S8 Conversion 2024-12-23 21:43:10 -06:00
Isaac Marovitz
967887d050 Upstream changes 2024-12-23 21:43:04 -06:00
Isaac Marovitz
3258a4bad1 Logic Operations 2024-12-23 21:43:00 -06:00
Isaac Marovitz
058ce78d31 Fix array size query 2024-12-23 21:42:56 -06:00
Isaac Marovitz
b988c0bc3d Debug Groups 2024-12-23 21:42:51 -06:00
Isaac Marovitz
1fc96128fa IaIndexing
Fixes shader problems in Donkey Kong Country Tropical Freeze, and Fire Emblem: Three Houses
2024-12-23 21:42:49 -06:00
Isaac Marovitz
4a11cc9c7a Rasterizer Discard + Multisample State 2024-12-23 21:42:47 -06:00
Isaac Marovitz
782299c123 Metal: Argument Buffer Pre-Pass (#38)
* Init

* Fix missing flags

* Cleanup
2024-12-23 21:42:44 -06:00
riperiperi
e8de1156eb Patch some leaks and only perform copies on valid textures (#37) 2024-12-23 21:42:42 -06:00
Isaac Marovitz
ceaa174859 Get render command encoder after finalising buffers
Fixes crash in Fire Emblem: Houses
2024-12-23 21:42:40 -06:00
Isaac Marovitz
7eaaeea999 Format 2024-12-23 21:42:38 -06:00
Isaac Marovitz
d707273671 Fix non atomic image loads again 2024-12-23 21:42:23 -06:00
Isaac Marovitz
2a7375209e Finally fix (most) image atomics 2024-12-23 21:42:22 -06:00
Isaac Marovitz
2e5ae70b91 Precise Float Fixes
Fixes artifacts in TOTK
2024-12-23 21:42:19 -06:00
Isaac Marovitz
6f24b88e88 Fix image atomics 2024-12-23 21:42:17 -06:00
Isaac Marovitz
5d59c552e7 Fix Non-Float Textures + Image Read + FSI Buffers
Fixes Mario Party Superstars
2024-12-23 21:42:15 -06:00
Isaac Marovitz
341e4e5fb1 Image Constant Fixes
Allows Mario Party Superstars to boot
2024-12-23 21:42:10 -06:00
Isaac Marovitz
68146fa285 Helper Shader fixes for non float formats 2024-12-23 21:41:44 -06:00
Isaac Marovitz
80bb95dfb9 Shader Extra Set Support + Cleanup (#36)
Separate samplers are now supported and arrays in constant sets are bound
2024-12-23 21:41:39 -06:00
Isaac Marovitz
5b88ea66ba InstGenMemory Refactor + Bindless Support 2024-12-23 21:41:35 -06:00
Isaac Marovitz
9d3fc82484 TextureArray & ImageArray Creation + State 2024-12-23 21:41:33 -06:00
Isaac Marovitz
8f6eceaa1f Fix hex number type ambiguity
Fixes cutscenes in Super Mario Sunshine
2024-12-23 21:41:30 -06:00
Isaac Marovitz
04bd1fa1ca Use RGBA8Unorm for R4G4B4A4Unorm
Gets SM64 to boot
2024-12-23 21:41:28 -06:00
Isaac Marovitz
c0dcb6c4f2 Dual Source Blend Support in Shader
Fixes Super Mario Galaxy and The Legend of Zelda: Skyward Sword HD
2024-12-23 21:41:26 -06:00
Isaac Marovitz
8bbfa86538 Get Tomb Raider working 2024-12-23 21:41:23 -06:00
Isaac Marovitz
a0c67cab44 Remove DummyBufferTextures
Mostly gets VTG on Compute working again
2024-12-23 21:41:19 -06:00
Isaac Marovitz
421ba5448a Properly register TextureBuffer usage + Store Auto ref 2024-12-23 21:41:12 -06:00
Isaac Marovitz
45c99dbfa8 Partial indirect draw support 2024-12-23 21:41:05 -06:00
Gabriel A
a84ed6af0d Fixes 2024-12-23 21:40:57 -06:00
Gabriel A
0642df4909 Start building more accurate vertex as compute usage info 2024-12-23 21:40:55 -06:00
Isaac Marovitz
d650538138 Bind TextureBuffers 2024-12-23 21:40:53 -06:00
Isaac Marovitz
eeadf17f5c Remove ClearSegments for now
Currently unimplemented and issues are arising with building BindingSegments in general.
2024-12-23 21:40:51 -06:00
Isaac Marovitz
7bf0625075 Fix compute generation failure in NieR 2024-12-23 21:40:49 -06:00
Isaac Marovitz
5220ee1dc8 Some debug improvements 2024-12-23 21:40:46 -06:00
Isaac Marovitz
cb0a1ce48a Stop complaining about clip distance 2024-12-23 21:40:44 -06:00
Isaac Marovitz
ffb9040b3b Shader Gen Fixes
Fixes Luigi’s Mansion 2 HD
2024-12-23 21:40:42 -06:00
Isaac Marovitz
40ea153616 DepthStencil Blits 2024-12-23 21:40:40 -06:00
Isaac Marovitz
4373610790 Multisample Blits
Partially fixes Sonic Colors Ultimate
2024-12-23 21:40:38 -06:00
Isaac Marovitz
f5b82cd6dc Fix image bindings 2024-12-23 21:40:36 -06:00
Isaac Marovitz
0064afeb6a FSI (with raster order groups) 2024-12-23 21:40:34 -06:00
Isaac Marovitz
69bee52a89 SwizzleAdd (NOT TESTED) 2024-12-23 21:40:32 -06:00
Isaac Marovitz
159afd5d03 Consolodate barriers 2024-12-23 21:40:30 -06:00
Isaac Marovitz
6229f3bb4c Shader Memory Barriers
Fixes some of the shader generation failures in Sonic Frontiers
2024-12-23 21:40:26 -06:00
Isaac Marovitz
d42f0e5945 Image binding support
Kirby still has a problem with NaN 3D Texture
2024-12-23 21:40:22 -06:00
Isaac Marovitz
015f5d00b4 Image shader gen support 2024-12-23 21:39:56 -06:00
Isaac Marovitz
d9b322688c Shader cache support 2024-12-23 21:39:54 -06:00
Isaac Marovitz
5e72eb8362 Make dotnet format happy 2024-12-23 21:39:51 -06:00
Isaac Marovitz
e0cd935c28 GAL ResourceUsage Changes
TODO: Guest Barrier Defer
2024-12-23 21:39:49 -06:00
Isaac Marovitz
1f133040bd Better vertex buffer management 2024-12-23 21:39:47 -06:00
Isaac Marovitz
c399868ddf Fix LOD 2024-12-23 21:39:45 -06:00
Isaac Marovitz
fcd2adecc5 Better index buffer management 2024-12-23 21:39:43 -06:00
Isaac Marovitz
b468569665 Formatting cleanup 2024-12-23 21:39:36 -06:00
Isaac Marovitz
353a6ca4bb Formatting 2024-12-23 21:39:31 -06:00
Isaac Marovitz
6188872a7c Update binding model description comment 2024-12-23 21:39:16 -06:00
riperiperi
8411f69899 Fix preload cbs optimization (for real) (#34)
* Mostly fix preload cbs. There seems to be some random flickering...

* fix index buffer usage range

* fix missing preflush submit before present
2024-12-23 21:39:13 -06:00
Isaac Marovitz
0bb0ecb599 Fix invariant position not doing its job 2024-12-23 21:39:11 -06:00
riperiperi
8cbd44aecb implement compressed/uncompressed copy, fix other copies, fix int/uint output shaders (#33) 2024-12-23 21:39:09 -06:00
Isaac Marovitz
d2f965885a Fix blend state optimisation breaking attachments
Fixes SM3DW
2024-12-23 21:39:07 -06:00
riperiperi
29b6e8ac53 Fix warnings 2024-12-23 21:39:05 -06:00
riperiperi
26da57cccd Maintain identity swizzle view of textures for rendering 2024-12-23 21:39:03 -06:00
riperiperi
82b5f8e681 Fix a bunch of issues with texture copy and flush (#32)
* Fix a bunch of issues with texture copy and flush

* TextureCopy helper class, fix clear bug
2024-12-23 21:38:59 -06:00
Isaac Marovitz
58527e02ee Cleanup + Format 2024-12-23 21:38:54 -06:00
Isaac Marovitz
4d5b128a81 Fix trying to reserve size 0 in staging buffer 2024-12-23 21:38:46 -06:00
Isaac Marovitz
49a814a400 Fix zero buff not being reset 2024-12-23 21:38:42 -06:00
Isaac Marovitz
09546205b5 Dirty Arg Buffers on Program Change 2024-12-23 21:38:39 -06:00
Isaac Marovitz
d2a4a9e9a7 Least allocations in the west 2024-12-23 21:38:37 -06:00
Isaac Marovitz
d3f273cad1 Don’t use Enum.HasFlag 2024-12-23 21:38:35 -06:00
Isaac Marovitz
60722a1837 Metal: Better Bindings (#29)
* Tell GAL to use Vk model (and break everything)

* ResourceBindingSegments

* Set information on backend caps

* Get ready to break everything

* Refactor EncoderStateManager

* Remove padding from helper shaders

* Fix ref array sizes

* Seperate vert & frag buffers

* Shader-side changes

* Fixes

* Fix some helper shader resource layouts

* Sort by binding id

* Fix helper shader layouts

* Don’t do inline vertex buffer updates

* Check for null storage
2024-12-23 21:38:32 -06:00
Isaac Marovitz
549938e2b1 Update comment for Metal 2024-12-23 21:38:20 -06:00
Isaac Marovitz
144397c3da Don’t do inline vertex buffer updates
Somehow broke zero buff MTLVertexDescriptor, but fixes broken geoemtry so I’m pushing anyway
2024-12-23 21:38:17 -06:00
riperiperi
2fb3c6975e Create command buffers when rented rather than in advance (#31)
* Make it less likely to freeze, but the creation of the command buffer should probably be moved

* Create command buffers as they're rented rather than in advance
2024-12-23 21:38:15 -06:00
riperiperi
879c93cf73 Preload command speedup, Texture/buffer data flush, blit shader fix (#30)
* Move encoder state to be tied to command buffer, so preload and background cbs have their own encoder state

* Texture buffer/data flush, blit shader fix
2024-12-23 21:38:13 -06:00
Isaac Marovitz
7d5b4c5d1d Dont bind images in texture slots 2024-12-23 21:38:11 -06:00
Isaac Marovitz
2860db198c Stop depth/stencil blits from crashing everything 2024-12-23 21:38:04 -06:00
riperiperi
4b53d18bef Fix Geometry/TFB on compute, Buffer Textures, add Window Resizing (#28) 2024-12-23 21:38:00 -06:00
riperiperi
bbbc9e529d State and cache optimization (#27)
* WIP pipeline/depth state cache rework

* Fix some issues

* Fix some more default values

* Reduce allocations for state changes

* fix helpershader stuff

* explanation comment

* fix depth bias
2024-12-23 21:37:58 -06:00
Isaac Marovitz
c160810bfc Fragment input interpolation qualifiers
Fixes Mario’s shadow in SMO
2024-12-23 21:37:55 -06:00
Isaac Marovitz
4f7b3fa058 CommandBufferBarrier 2024-12-23 21:37:52 -06:00
riperiperi
36de337ac2 Add constrained border colours to samplers (#26) 2024-12-23 21:37:50 -06:00
Isaac Marovitz
3b2beda27f Don’t bind byte format converted index buffers at requested index 2024-12-23 21:37:48 -06:00
Isaac Marovitz
de8e03c350 Render target deduplication
not sure if this is working
2024-12-23 21:37:45 -06:00
Isaac Marovitz
0b6bc12a65 Fix CBP not doing its job
Thanks peri (again)
2024-12-23 21:37:43 -06:00
Isaac Marovitz
16bc02ea2a Fix blend descriptors not dirting render pipeline
Thanks peri
2024-12-23 21:37:41 -06:00
Isaac Marovitz
97814b2852 Support non-index quad draws
Fixes Deltarune
2024-12-23 21:37:38 -06:00
Isaac Marovitz
a9633981a8 Be better about memory 2024-12-23 21:37:36 -06:00
Isaac Marovitz
1af7dc4b68 Fix stencil clears 2024-12-23 21:37:14 -06:00
Isaac Marovitz
d6dcc39131 Enable Alpha Test workaround on Metal 2024-12-23 21:37:10 -06:00
Isaac Marovitz
4f699ef96a Fix Cull FrontAndBack 2024-12-23 21:37:07 -06:00
Isaac Marovitz
30f194e5c0 Warning about host map buffer creation 2024-12-23 21:37:05 -06:00
Isaac Marovitz
f1086afcdf Fix fragment point_coord in 2024-12-23 21:37:03 -06:00
Isaac Marovitz
dae0f3cded Argument Buffers (#24)
* Stuff

* More arg buffer stuff

* Fixes

* Rebase

* Pass storage buffers to inline functions

* Fix binding

* Fix typo + Fix a couple shaders

* Enforce ids

* Dispose

* Mark used buffers as resident

* Update depth clear shader

* Fix non-contiguous struct defs

* Update ChangeBufferStride

* Fix StorageBuffer assignments

* Fix odyssey crash

* Retain buffer bindings

* Pad Std140

* Set texture data with safe buffers

* Clone buffers

* Always declare vert in

* Stop clears from breaking OpenGL games

* Fix depth clear

* Use invariant position

* Horribly inefficient texture & sampler arg buffers

* Fix missing struct access

* Minimise rebinds as much as possible

* Build arg buffers on staging buffer
2024-12-23 21:37:01 -06:00
Isaac Marovitz
a1ab7fe6a2 VoteAllEqual, FindLSB/MSB 2024-12-23 21:36:58 -06:00
Isaac Marovitz
fac2cbbbbf Fix vertex “built-ins”
Only declare main func out in main

Fix simd_ballot

Fix thread_index_in_simdgroup outside of compute

Fix atomic operations

instance_index
2024-12-23 21:36:56 -06:00
Isaac Marovitz
9b138a413c Actually clear the right render target 2024-12-23 21:36:54 -06:00
Isaac Marovitz
58eefa8bdf Big GetData()
Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-12-23 21:36:52 -06:00
Isaac Marovitz
0ebc8bd1b8 Fix Animal Crossing Crash 2024-12-23 21:36:50 -06:00
Isaac Marovitz
becf828d0a Instruction.Barrier
Whoops

Fix inline functions in compute stage

Fix regression

Declare SharedMemories + Only Declare Memories on Main Func

Lowecase struct

Avoid magic strings

Make function signatures readable

Change how unsized arrays are indexed

Use string builder

Fix shuffle instructions

Cleanup NumberFormater

Bunch of Subgroup I/O Vars

Will probably need further refinement

Fix point_coord type

Fix support buffer declaration

Fix point_coord
2024-12-23 21:36:48 -06:00
Isaac Marovitz
d0e4adac36 PreloadCbs + FlushCommandsIfWeightExceeding 2024-12-23 21:36:46 -06:00
Isaac Marovitz
197184657f Cleanup Pipeline
Housekeeping

More housekeeping
2024-12-23 21:36:44 -06:00
Isaac Marovitz
74083083cd PersistentFlushBuffer + BackgroundResources 2024-12-23 21:36:42 -06:00
Isaac Marovitz
175cded85d Match S8UintD24Unorm to Depth24UnormStencil8
Kind of works for es2gears
2024-12-23 21:36:40 -06:00
Isaac Marovitz
c911db8309 Fix FEZ not showing anything
Does not fix the underlying shortcomings of the cache system
2024-12-23 21:35:50 -06:00
Isaac Marovitz
3451fbbbad Clear cached converted buffers on signaled write 2024-12-23 21:35:47 -06:00
Isaac Marovitz
98ae46ba70 FIx regression 2024-12-23 21:35:45 -06:00
Isaac Marovitz
bf4232a35b Helper shader cleanup 2024-12-23 21:35:43 -06:00
Isaac Marovitz
c3e39a9c91 Use buffer manager for color blit 2024-12-23 21:35:41 -06:00
Isaac Marovitz
b8779c6e09 Buffer Conversions (#23)
* Why is this not working

* Revert helper shader changes for now

* Byte Index Buffer Restride
2024-12-23 21:35:38 -06:00
riperiperi
20cf1a08c1 don't recreate render pipeline unless we're about to draw, pass view depth properly (#22) 2024-12-23 21:35:36 -06:00
Isaac Marovitz
dda746c0fb Metal: Buffers Take 2 (#21)
* Basic BufferManager

* Start Scoped Command Buffers

* Fences stuff

* Remember to cleanup sync manager

* Auto, Command Buffer Dependants

* Cleanup

* Cleanup + Fix Texture->Buffer Copies

* Slow buffer upload

* Cleanup + Rework TextureBuffer

* Don’t get unsafe

* Cleanup

* Goddamn it

* Staging Buffer + Interrupt Action + Flush
2024-12-23 21:35:33 -06:00
Isaac Marovitz
585bdc2b54 Log failed format conversions 2024-12-23 21:35:31 -06:00
Isaac Marovitz
e49702965f Print shader code involved in failed linking 2024-12-23 21:35:21 -06:00
Isaac Marovitz
8b5392ce9b Don’t use DidModifyRange 2024-12-23 21:35:17 -06:00
Isaac Marovitz
9e61294671 Fix sample compare 2024-12-23 21:35:15 -06:00
Isaac Marovitz
5423ad9ae1 Depth Bias 2024-12-23 21:35:13 -06:00
Isaac Marovitz
65bddcd475 Map R5G5B5A1Unorm 2024-12-23 21:35:11 -06:00
Samuliak
36ac0414e2 override Equals for render pipeline hash 2024-12-23 21:35:09 -06:00
Isaac Marovitz
51b4ffeb6c Disable scaled vertex formats 2024-12-23 21:35:07 -06:00
Isaac Marovitz
39bfd55958 Disable Vector Indexing Bug Workaround 2024-12-23 21:35:04 -06:00
Isaac Marovitz
e615d7d849 Fix modulo operator
Support sample offsets

Include FragmentIn as additional arg

Always declare frag output struct

SubgroupLaneId
2024-12-23 21:35:02 -06:00
Isaac Marovitz
c883ebb645 Workaround for Wonder 2024-12-23 21:35:00 -06:00
Isaac Marovitz
24ab7788d8 Fix 3D -> 3D Texture Copies 2024-12-23 21:34:57 -06:00
Isaac Marovitz
9ab2cd94c1 Fix Clear Viewport 2024-12-23 21:34:55 -06:00
Isaac Marovitz
4ee4e09358 Fix sample-less reads with lod 2024-12-23 21:34:52 -06:00
Isaac Marovitz
a07975afec Fix Pack and UnpackHalf2x16 2024-12-23 21:34:50 -06:00
Isaac Marovitz
746c897206 Handle Array Format SetData 2024-12-23 21:34:48 -06:00
Isaac Marovitz
fe4fa6f4db Cleanup 2024-12-23 21:33:31 -06:00
Isaac Marovitz
db24e0f6fe Implement IoVariable.FrontFacing 2024-12-23 21:33:24 -06:00
Isaac Marovitz
e9aee16a27 Fix LOD sample typo 2024-12-23 21:33:21 -06:00
Isaac Marovitz
02f1e289e2 Rebase Changes 2024-12-23 21:33:19 -06:00
Isaac Marovitz
9ebf82f184 More cleanup 2024-12-23 21:33:17 -06:00
Isaac Marovitz
49e83335d1 Cleanup + Format 2024-12-23 21:33:14 -06:00
Isaac Marovitz
f00cf8704f Metal: Compute Shaders (#19)
* check for too bix texture bindings

* implement lod query

* print shader stage name

* always have fragment input

* resolve merge conflicts

* fix: lod query

* fix: casting texture coords

* support non-array memories

* use structure types for buffers

* implement compute pipeline cache

* compute dispatch

* improve error message

* rebind compute state

* bind compute textures

* pass local size as an argument to dispatch

* implement texture buffers

* hack: change vertex index to vertex id

* pass support buffer as an argument to every function

* return at the end of function

* fix: certain missing compute bindings

* implement texture base

* improve texture binding system

* remove useless exception

* move texture handle to texture base

* fix: segfault when using disposed textures

---------

Co-authored-by: Samuliak <samuliak77@gmail.com>
Co-authored-by: SamoZ256 <96914946+SamoZ256@users.noreply.github.com>
2024-12-23 21:33:12 -06:00
Isaac Marovitz
65da0569a3 Handle stride 0 on regular buffers 2024-12-23 21:33:09 -06:00
Isaac Marovitz
d811532a9f Buffer Descriptor Step Functions 2024-12-23 21:33:07 -06:00
Isaac Marovitz
a42c66e6d5 Sample LOD Level 2024-12-23 21:33:01 -06:00
Isaac Marovitz
8be6b671b8 Fix FragmentOutputColor Type 2024-12-23 21:32:59 -06:00
Isaac Marovitz
2e99df371f Stencil Ref Value 2024-12-23 21:32:56 -06:00
Isaac Marovitz
ac8af32744 Stencil Fixes 2024-12-23 21:32:54 -06:00
Isaac Marovitz
a58568d036 RenderTargetColorMasks 2024-12-23 21:32:52 -06:00
Isaac Marovitz
7ed45d12db Make dotnet format happy 2024-12-23 21:32:50 -06:00
SamoZ256
505f830556 Zero vertex buffer (#17)
* cast src size to float

* implement zero buffers
2024-12-23 21:32:47 -06:00
Isaac Marovitz
43ad627d4f Implement Texture CopyTo 2024-12-23 21:32:45 -06:00
Isaac Marovitz
c5cca8a1a3 Cleanup present 2024-12-23 21:32:43 -06:00
Isaac Marovitz
f7941a0a8b Metal: Advanced Present (#6)
* Initial DrawTexture support & Advanced Present

* TODO: Get Scissors Working

* Chnage scissor state management

* Rebase problems…

* Rebase fixes again

* Update DrawTexture + Fix Topology

* Fix flipping

* Add clear action support

* Cleanup
2024-12-23 21:32:40 -06:00
SamoZ256
6cc4d46e8c Clone the state & flip viewport vertically (#16)
* implement texture get data

* reset all state before blit & clone state

* format

* support blit regions

* implement source region for blit

* replace bottom with top

* account for 0 size

* support image flipping

* revert presentation fixes & y flip

* revert

* flip viewport vertically

* switch face winding

* comment

* use SetBytes for texture clear

* implement missing compute builtins

* change storage and texture buffer alignment

* correct compute builtins

* don't use nullable for textures and samplers

* remove incorrect texture get data implementation

* Cleanup IntPtrs

---------

Co-authored-by: Isaac Marovitz <isaacryu@icloud.com>
2024-12-23 21:32:34 -06:00
SamoZ256
18b852e05d Fix Scott Pilgrim (#15)
* check for null vertex functions

* format

* Format

---------

Co-authored-by: Isaac Marovitz <isaacryu@icloud.com>
2024-12-23 21:32:02 -06:00
Isaac Marovitz
41e6a04a23 Suppress GC Finalize on StateCache 2024-12-23 21:31:59 -06:00
Isaac Marovitz
881ab59177 Use Stack instead of List 2024-12-23 21:31:57 -06:00
Isaac Marovitz
327c1576f7 Whitespace formatting 2024-12-23 21:31:54 -06:00
Samuliak
60ece6d9a1 dispose all temporary buffers 2024-12-23 21:31:52 -06:00
Samuliak
58b42a1143 dispose temporary metal buffer 2024-12-23 21:31:47 -06:00
Samuliak
3be47ae4a9 dispose drawable texture view 2024-12-23 21:31:41 -06:00
Samuliak
6ab989ac54 implement texture get data 2024-12-23 21:31:36 -06:00
Samuliak
9f01cce95f fix: don't dispose stencil state before using 2024-12-23 21:31:30 -06:00
Samuliak
0abbbdc277 allow null depth stencil render targets 2024-12-23 21:31:28 -06:00
Samuliak
eb7ec713ec reset certain state before doing blit or clear 2024-12-23 21:31:26 -06:00
Samuliak
d5437f3dbf reset viewport before blit 2024-12-23 21:31:24 -06:00
Samuliak
1c4e527ac2 dispose encoder state manager 2024-12-23 21:31:01 -06:00
Samuliak
af341f88df dispose all objects in encoder state manager 2024-12-23 21:30:58 -06:00
Samuliak
1ff81393be dispose caches 2024-12-23 21:30:55 -06:00
Samuliak
2cb5265c8e warn about barriers 2024-12-23 21:30:53 -06:00
Samuliak
78553f31d9 do memory barriers 2024-12-23 21:30:50 -06:00
Samuliak
60084f826e remove useless parameters 2024-12-23 21:30:48 -06:00
Samuliak
280efb2ed6 set the inline state after restoring state 2024-12-23 21:30:46 -06:00
Samuliak
14607f4471 make states private 2024-12-23 21:30:41 -06:00
Samuliak
fd4fe01348 fix: incorrect merge stuff 2024-12-23 21:30:39 -06:00
Samuliak
8f91b556af don't interrupt render pass before color clear 2024-12-23 21:30:29 -06:00
Samuliak
305a703d4a implement save and restore state system 2024-12-23 21:30:22 -06:00
Samuliak
a2c0c11380 revert deferred clears 2024-12-23 21:30:02 -06:00
Samuliak
016df3b050 prepare for deferred clears 2024-12-23 21:29:58 -06:00
Samuliak
084b75a398 resolve merge conflicts 2024-12-23 21:29:56 -06:00
Isaac Marovitz
91aed4d0dd Rebase 2024-12-23 21:29:54 -06:00
Isaac Marovitz
bea46ff9ce Cleanup + Format 2024-12-23 21:29:50 -06:00
Isaac Marovitz
58fb8564a8 Start Proper Dispose 2024-12-23 21:29:47 -06:00
Samuliak
94e077ca27 do texture barrier tiled 2024-12-23 21:29:44 -06:00
Samuliak
a10b0230c3 do texture barrier 2024-12-23 21:29:42 -06:00
Samuliak
c7dc9ba34e implement depth stencil cache 2024-12-23 21:29:40 -06:00
Isaac Marovitz
ad4db6b242 Fix typo in SamplerType.TextureBuffer 2024-12-23 21:29:37 -06:00
Isaac Marovitz
11c596a18a Fix StoreActions & Don’t Clamp Scissor for Now 2024-12-23 21:29:35 -06:00
Isaac Marovitz
7f8d54d6dc Depth Clear 2024-12-23 21:29:33 -06:00
Isaac Marovitz
90e3899c23 Shitty Clears + Inline Buffer Improvements? 2024-12-23 21:29:24 -06:00
Isaac Marovitz
381f4ec091 FragmentOutputDepth Fixes 2024-12-23 21:29:20 -06:00
Isaac Marovitz
b76f9105c8 Depth Sampler Fixes 2024-12-23 21:29:09 -06:00
Isaac Marovitz
486fd78eba Revert position changes 2024-12-23 21:29:06 -06:00
Isaac Marovitz
38385bad30 Nvm it should be in.position 2024-12-23 21:29:04 -06:00
Isaac Marovitz
27ece39dbe More shader fixes 2024-12-23 21:29:02 -06:00
Samuliak
7a6c7196c8 fix: incorrect layer count of texture view 2024-12-23 21:28:59 -06:00
Samuliak
765ca8e6c0 don't use mask on size query 2024-12-23 21:28:57 -06:00
Samuliak
d5b98d6187 declare local memory 2024-12-23 21:28:51 -06:00
Samuliak
913f25b2a0 support multiple render targets & fix: incorrect texture name 2024-12-23 21:28:41 -06:00
Samuliak
5d0ae23a0b put render pipeline cache into a separate file 2024-12-23 21:28:38 -06:00
Samuliak
dd5fb8bed9 implement pipeline cache 2024-12-23 21:28:36 -06:00
Isaac Marovitz
2316f30de1 Use return value of BeginRenderPass 2024-12-23 21:28:33 -06:00
Isaac Marovitz
96eea9de23 Cleanup 2024-12-23 21:28:31 -06:00
Samuliak
9c5917912b remove outdated comment 2024-12-23 21:28:29 -06:00
Isaac Marovitz
efe575c9b2 Fix table 2024-12-23 21:28:27 -06:00
Isaac Marovitz
ba4d6815ea Dont hardcode Vertex Format 2024-12-23 21:28:20 -06:00
Samuliak
9b99f55c4f style 2024-12-23 21:28:17 -06:00
Samuliak
5a6169b19d bring back inline updates for some state 2024-12-23 21:28:15 -06:00
Samuliak
f30aa98ce5 fix: don't rebind pipeline unless dirty 2024-12-23 21:28:12 -06:00
Samuliak
149141594f don't bind null vertex buffers 2024-12-23 21:28:09 -06:00
Samuliak
b5f15de64f mark state as dirty 2024-12-23 21:28:07 -06:00
Samuliak
bc9a26bbf2 add todo notice 2024-12-23 21:28:04 -06:00
Samuliak
f3d314104f don't end render pass when not neccessary 2024-12-23 21:28:01 -06:00
Isaac Marovitz
6324569dd2 Remove rebase garbage 2024-12-23 21:27:30 -06:00
Isaac Marovitz
2c474050f8 Be smart and use a bitmask not a list 2024-12-23 21:27:18 -06:00
Isaac Marovitz
46cc993f9a Cleanup 2024-12-23 21:27:15 -06:00
Isaac Marovitz
c51c8bdae6 Fix Vertex Attributes in Wonder & Kirby 2024-12-23 21:27:13 -06:00
Isaac Marovitz
4f356b4117 Implement SetDepthClamp 2024-12-23 21:27:10 -06:00
Isaac Marovitz
15051d6e56 Implement SetBlendState 2024-12-23 21:27:08 -06:00
Isaac Marovitz
2587e1ff22 Be consistent with things that lack support 2024-12-23 21:27:06 -06:00
Isaac Marovitz
0d5292ff8c Ignore SetDepthMode 2024-12-23 21:27:04 -06:00
Isaac Marovitz
401ad1f983 Make Texture Volatile on dispose 2024-12-23 21:26:57 -06:00
Isaac Marovitz
3c1ef06151 Format 2024-12-23 21:26:49 -06:00
Isaac Marovitz
95af212cfc Fix present 2024-12-23 21:26:46 -06:00
Isaac Marovitz
4190abbbf5 Fix Depth/Stencil attachments 2024-12-23 21:25:27 -06:00
Isaac Marovitz
c7b6e4cf80 Break everything :D 2024-12-23 21:25:24 -06:00
Isaac Marovitz
96d884a15b Clamp ScissorRect 2024-12-23 21:25:21 -06:00
Isaac Marovitz
f79ebd1141 Set DepthAttachmentPixelFormat 2024-12-23 21:25:19 -06:00
Isaac Marovitz
7f65ec0b8c Set Depth Attachment Texture 2024-12-23 21:25:17 -06:00
Isaac Marovitz
824321c88a Clamp Viewport ZNear & ZFar 2024-12-23 21:25:08 -06:00
Samuliak
91d1bb6c08 format 2024-12-23 21:24:50 -06:00
Samuliak
22d3fa068d use 0 instead of undef 2024-12-23 21:24:45 -06:00
Samuliak
d08218a809 fix: pass array index as an additional argument to sample 2024-12-23 21:24:42 -06:00
Samuliak
b7414c1e4d don't declare samplers for separate textures 2024-12-23 21:24:40 -06:00
Samuliak
a66ab905a9 don't hardcode texture type 2024-12-23 21:24:37 -06:00
Samuliak
2bef29b200 offset storage buffer bindings by 15 2024-12-23 21:24:35 -06:00
Samuliak
72eb47513c fix: incorrect abs instruction 2024-12-23 21:24:32 -06:00
Samuliak
6095f14646 add: vertex and instance id arguments 2024-12-23 21:24:29 -06:00
Samuliak
c3a9a0d625 determine type of buffer by its field types 2024-12-23 21:24:03 -06:00
Isaac Marovitz
18e1569941 Rebase + Format 2024-12-23 21:23:59 -06:00
Samuliak
362dc6eaea use unknown texture usage 2024-12-23 21:23:47 -06:00
Samuliak
d66c39b64b don't hardcode render pipeline attachments 2024-12-23 21:23:42 -06:00
Samuliak
de23abcf90 create GetSwizzle helper function 2024-12-23 21:23:32 -06:00
Samuliak
b2a0ca0e2b add: textures and samplers as shader arguments & fix: issue with casting 2024-12-23 21:23:28 -06:00
Samuliak
e3364b0fcc support fragment coord as an input to a shader 2024-12-23 21:23:24 -06:00
Samuliak
8a0dd491b9 support texture views 2024-12-23 21:23:20 -06:00
Isaac Marovitz
ba05ed9552 Format 2024-12-23 21:23:14 -06:00
Isaac Marovitz
b85721b738 Rebase + GAL Changes 2024-12-23 21:23:06 -06:00
Isaac Marovitz
9d7164a329 Remove TODOs 2024-12-23 21:21:51 -06:00
Isaac Marovitz
2f70337dca Fix Scissor/Viewport state & Validation Error 2024-12-23 21:21:47 -06:00
Isaac Marovitz
60c99e32b0 Require Argument Buffers Tier 2 2024-12-23 21:21:42 -06:00
Isaac Marovitz
ebd2d82ff3 Buffer bindings in shader…
Will need to be reworked
2024-12-23 21:21:36 -06:00
Isaac Marovitz
44bd12104b Bind Uniform & Storage Buffers 2024-12-23 21:21:31 -06:00
Evan Husted
0df70db73c remnant 2024-12-23 21:21:14 -06:00
Isaac Marovitz
64e9dcee3d Fix buffer access syntax 2024-12-23 21:19:39 -06:00
Isaac Marovitz
e353e3d3fc Dispose pipeline before window 2024-12-23 21:19:33 -06:00
Isaac Marovitz
6a67822b3b Set scissors & viewports 2024-12-23 21:19:26 -06:00
Isaac Marovitz
65b7af6308 Format 2024-12-23 21:19:22 -06:00
Isaac Marovitz
36fe41bffd Format 2024-12-23 21:19:19 -06:00
Isaac Marovitz
e758e531c5 Fix some crashes 2024-12-23 21:19:16 -06:00
Isaac Marovitz
efa9d56a56 Fix Cubemap & Array Texture Creation 2024-12-23 21:19:08 -06:00
Isaac Marovitz
b95e1d288b Properly check for 3D 2024-12-23 21:17:32 -06:00
Isaac Marovitz
48aba086e1 Fix swizzle for certain formats 2024-12-23 21:17:28 -06:00
Isaac Marovitz
44f4d41cf8 Blit at the end of the render 2024-12-23 21:17:25 -06:00
Isaac Marovitz
b4f468c653 Load attachments 2024-12-23 21:17:21 -06:00
Isaac Marovitz
3117aeca7f Cleanup Shader I/O 2024-12-23 21:17:17 -06:00
Isaac Marovitz
987a42ce30 Fix fragment shader bindings 2024-12-23 21:17:09 -06:00
Isaac Marovitz
fc7f09624c Fix VertexBuffers
Naive non-managed approach
2024-12-23 21:16:57 -06:00
Isaac Marovitz
e2445990a5 Fix some shader gen problems… 2024-12-23 21:16:52 -06:00
Isaac Marovitz
dc4305f1cf Formatting 2024-12-23 21:16:48 -06:00
Isaac Marovitz
b7a0aefa80 Make TypeConversion failure an error 2024-12-23 21:16:42 -06:00
Isaac Marovitz
7a99143a8a Fix MSL Reinterpret Casts 2024-12-23 21:16:39 -06:00
Isaac Marovitz
89d1caf30f Dont set Vertex Attributes for now 2024-12-23 21:16:35 -06:00
Isaac Marovitz
de7b3e7dac Remove capture code 2024-12-23 21:16:31 -06:00
Isaac Marovitz
94e15aa662 Bind Textures & Samplers 2024-12-23 21:16:27 -06:00
Isaac Marovitz
b157a8e549 Revise ISampler 2024-12-23 21:16:20 -06:00
Isaac Marovitz
6685041545 Try again 2024-12-23 21:16:15 -06:00
Isaac Marovitz
b8630b5c45 Resolve warning 2024-12-23 21:16:12 -06:00
Isaac Marovitz
c0da3d68ca Formatting 2024-12-23 21:16:07 -06:00
Isaac Marovitz
76bafe75f4 FIx build 2024-12-23 21:15:56 -06:00
Isaac Marovitz
3c562d8906 Fix some rebase errors 2024-12-23 21:15:36 -06:00
Isaac Marovitz
df0dc4454b End Pass on Dispose 2024-12-23 21:15:33 -06:00
Isaac Marovitz
26ea1e6d37 Don’t change Render State if Vertex Function is Invalid 2024-12-23 21:15:26 -06:00
Isaac Marovitz
e8d0212ec6 “Report” Driver 2024-12-23 21:15:18 -06:00
Isaac Marovitz
e7197877a2 Adjust function signature 2024-12-23 21:15:14 -06:00
Isaac Marovitz
dff9046f55 Get it building again 2024-12-23 21:15:07 -06:00
Isaac Marovitz
cb36036faa Render Targets 2024-12-23 21:15:01 -06:00
Isaac Marovitz
c4cf4895d8 format 2024-12-23 21:14:55 -06:00
Isaac Marovitz
b6116da940 Formatting 2024-12-23 21:14:50 -06:00
Isaac Marovitz
fbcd9994c8 smh 2024-12-23 21:14:18 -06:00
Isaac Marovitz
5d90932277 Dont specify [[stage_in]] on fragment 2024-12-23 21:14:06 -06:00
Isaac Marovitz
037157135e If one shader fails, whole program fails 2024-12-23 21:12:41 -06:00
Isaac Marovitz
d45c7711ba Fix fragment shaders (and fuck everything up) 2024-12-23 21:09:35 -06:00
Isaac Marovitz
b3629e3a8b Vertex buffer data 2024-12-23 21:09:25 -06:00
Isaac Marovitz
c216028d00 Dont be stupid 2024-12-23 21:09:18 -06:00
Isaac Marovitz
02fbcfbadb Dont set 0 attributes 2024-12-23 21:09:07 -06:00
Isaac Marovitz
a5c1b6a255 Reset Descriptor instead of making a new object 2024-12-23 21:09:00 -06:00
Isaac Marovitz
be1d099879 Set Vertex Descriptor properly 2024-12-23 21:08:54 -06:00
Isaac Marovitz
3529fcd592 Start vertex descriptor work 2024-12-23 21:08:44 -06:00
Isaac Marovitz
3398977c97 Implement CreateProgram 2024-12-23 21:08:35 -06:00
Isaac Marovitz
fe62c794b9 Fix fragment output color 2024-12-23 21:08:21 -06:00
Isaac Marovitz
2e3509f8e8 Set TargetLanguage for Metal to MSL 2024-12-23 21:08:06 -06:00
Isaac Marovitz
d65858be25 Fix IoMap variable names
Output struct

Lazy Vertex IO

Output fixes

Fix output struct definition

MSL Binding Model description

Might need tweaks/adjustments

Cleanup

Typo + Format
2024-12-23 21:07:58 -06:00
Isaac Marovitz
2a28950739 Fix ETC2 PTA formats
Format
2024-12-23 21:07:52 -06:00
Isaac Marovitz
4587905cd8 Partial TextureQuerySamples 2024-12-23 21:07:07 -06:00
Isaac Marovitz
9cc56a3bca Fix instructions 2024-12-23 21:06:58 -06:00
Isaac Marovitz
b06afd1a1f LDR ASTC 2024-12-23 21:06:52 -06:00
Isaac Marovitz
7182ac7233 Get build working again (values likely wrong) 2024-12-23 21:06:45 -06:00
Isaac Marovitz
398b6cb60e dotnet format 2024-12-23 21:06:31 -06:00
Isaac Marovitz
c5522e3694 Back to where we were
First special instruction

Start Load/Store implementation

Start TextureSample

Sample progress

I/O Load/Store Progress

Rest of load/store

TODO: Currently, the generator still assumes the GLSL style of I/O attributres. On MSL, the vertex function should output a struct which contains a float4 with the required position attribute.

TextureSize and VectorExtract

Fix UserDefined IO Vars

Fix stage input struct names
2024-12-23 21:05:34 -06:00
Isaac Marovitz
a3da70edc2 Boot TOTK 2024-12-23 21:05:28 -06:00
Evan Husted
14999a1d51
Merge branch 'master' into new-metal 2024-12-23 21:04:54 -06:00
Isaac Marovitz
94e699eeba Boot Sonic Mania 2024-12-23 21:01:19 -06:00
Isaac Marovitz
b1785c0b14 Update for new Shader IR format 2024-12-23 21:01:10 -06:00
Isaac Marovitz
84c90f8895 Update src/Ryujinx.Graphics.Metal/Pipeline.cs
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-12-23 21:00:54 -06:00
Isaac Marovitz
dc4d3078ef Vertex Input Attributes 2024-12-23 21:00:45 -06:00
Isaac Marovitz
4b5c3d7fc6 More Shader Gen Stuff
Mostly copied from GLSL since in terms of syntax within blocks they’re pretty similar. Likely the result will need tweaking…

Isn’t that conveniant?

“Do the simd_shuffle”

atomics

Remaining instructions

Remove removed special instructions

Getting somewhere…
2024-12-23 21:00:23 -06:00
Isaac Marovitz
5a802a550b Fix Metal Validation Error 2024-12-23 21:00:16 -06:00
Isaac Marovitz
7c31a411df SDL2 Headless Metal Backend support 2024-12-23 20:59:56 -06:00
Isaac Marovitz
8b9d6ffc94 Easier capture stuff 2024-12-23 20:58:47 -06:00
Isaac Marovitz
4cde7a4125 Define MaxFramesPerCapture 2024-12-23 20:58:39 -06:00
Isaac Marovitz
c1ef270b9d Cleanup encoder getting + Fix capture overflow 2024-12-23 20:58:32 -06:00
Evan Husted
2812f01643 merge leftovers 2024-12-23 20:58:17 -06:00
Isaac Marovitz
7441d94f10 Formatting 2024-12-23 20:57:31 -06:00
Isaac Marovitz
a8b4e643d0 Start of MSL instructions
Remaining functions
2024-12-23 20:55:40 -06:00
Isaac Marovitz
a7908c187d Warn when generating unsupported shader 2024-12-23 20:55:27 -06:00
Isaac Marovitz
25dba8da7c Pass sampler to Blit shader 2024-12-23 20:55:21 -06:00
Isaac Marovitz
0edec0d3ff Shader comments 2024-12-23 20:55:12 -06:00
Isaac Marovitz
fb8749ce4e HelperShaders class 2024-12-23 20:53:42 -06:00
Isaac Marovitz
d36c285b79 Undertale boots 2024-12-23 20:53:33 -06:00
Isaac Marovitz
179482e9cb Check if packed depth is supported 2024-12-23 20:53:19 -06:00
Isaac Marovitz
671aff68a6 Fix RGB Seizure 2024-12-23 20:53:06 -06:00
Isaac Marovitz
8bf33b3098 Barry is here mashallah 2024-12-23 20:52:40 -06:00
Isaac Marovitz
6a115becef Seizure my beloved is working 2024-12-23 20:52:32 -06:00
Isaac Marovitz
ed445e001a SetData 2024-12-23 20:52:22 -06:00
Isaac Marovitz
93f31bd08a Look ma no crash 2024-12-23 20:44:22 -06:00
Isaac Marovitz
a60ecea4c3 Whitespace 2024-12-23 20:44:19 -06:00
Isaac Marovitz
ff0362063a TODO 2024-12-23 20:44:13 -06:00
Isaac Marovitz
e0ea464c40 BeginComputePass 2024-12-23 20:44:01 -06:00
Isaac Marovitz
05002ae234 SetDepthTest 2024-12-23 20:43:52 -06:00
Isaac Marovitz
dc60b76748 SetStencilTest 2024-12-23 20:43:43 -06:00
Isaac Marovitz
cc3c7901b6 Forgot depth 2024-12-23 20:43:37 -06:00
Isaac Marovitz
15ad03bc04 Texture usage 2024-12-23 20:43:32 -06:00
Isaac Marovitz
1d01fbf6b1 CopyBuffer to Buffer 2024-12-23 20:43:10 -06:00
Isaac Marovitz
1e835aa56f CopyTo Buffer 2024-12-23 20:42:50 -06:00
Evan Husted
93e5ff0137 up to date with 08126b26b1db39b2eb11be81b4e2ffd613b0bdbf 2024-12-23 20:41:07 -06:00
Evan Husted
6d513cad1e merging leftovers 2024-12-23 20:36:29 -06:00
Evan Husted
cbad43b003 some initial metal commits cherry-picked 2024-12-23 20:35:49 -06:00
131 changed files with 14992 additions and 140 deletions

View File

@ -44,6 +44,7 @@
<PackageVersion Include="Gommon" Version="2.6.8" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview20" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />

View File

@ -80,6 +80,10 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
ProjectSection(ProjectDependencies) = postProject
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
@ -257,6 +261,10 @@ Global
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -6,7 +6,9 @@ namespace Ryujinx.Common.Configuration
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend
{
Auto,
Vulkan,
OpenGl,
Metal
}
}

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL
{
public readonly struct ComputeSize
{
public readonly static ComputeSize VtgAsCompute = new ComputeSize(32, 32, 1);
public readonly int X;
public readonly int Y;
public readonly int Z;
public ComputeSize(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
}
}

View File

@ -339,6 +339,84 @@ namespace Ryujinx.Graphics.GAL
return 1;
}
/// <summary>
/// Get bytes per element for this format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>Byte size for an element of this format (pixel, vertex attribute, etc)</returns>
public static int GetBytesPerElement(this Format format)
{
int scalarSize = format.GetScalarSize();
switch (format)
{
case Format.R8G8Unorm:
case Format.R8G8Snorm:
case Format.R8G8Uint:
case Format.R8G8Sint:
case Format.R8G8Uscaled:
case Format.R8G8Sscaled:
case Format.R16G16Float:
case Format.R16G16Unorm:
case Format.R16G16Snorm:
case Format.R16G16Uint:
case Format.R16G16Sint:
case Format.R16G16Uscaled:
case Format.R16G16Sscaled:
case Format.R32G32Float:
case Format.R32G32Uint:
case Format.R32G32Sint:
case Format.R32G32Uscaled:
case Format.R32G32Sscaled:
return 2 * scalarSize;
case Format.R8G8B8Unorm:
case Format.R8G8B8Snorm:
case Format.R8G8B8Uint:
case Format.R8G8B8Sint:
case Format.R8G8B8Uscaled:
case Format.R8G8B8Sscaled:
case Format.R16G16B16Float:
case Format.R16G16B16Unorm:
case Format.R16G16B16Snorm:
case Format.R16G16B16Uint:
case Format.R16G16B16Sint:
case Format.R16G16B16Uscaled:
case Format.R16G16B16Sscaled:
case Format.R32G32B32Float:
case Format.R32G32B32Uint:
case Format.R32G32B32Sint:
case Format.R32G32B32Uscaled:
case Format.R32G32B32Sscaled:
return 3 * scalarSize;
case Format.R8G8B8A8Unorm:
case Format.R8G8B8A8Snorm:
case Format.R8G8B8A8Uint:
case Format.R8G8B8A8Sint:
case Format.R8G8B8A8Srgb:
case Format.R8G8B8A8Uscaled:
case Format.R8G8B8A8Sscaled:
case Format.B8G8R8A8Unorm:
case Format.B8G8R8A8Srgb:
case Format.R16G16B16A16Float:
case Format.R16G16B16A16Unorm:
case Format.R16G16B16A16Snorm:
case Format.R16G16B16A16Uint:
case Format.R16G16B16A16Sint:
case Format.R16G16B16A16Uscaled:
case Format.R16G16B16A16Sscaled:
case Format.R32G32B32A32Float:
case Format.R32G32B32A32Uint:
case Format.R32G32B32A32Sint:
case Format.R32G32B32A32Uscaled:
case Format.R32G32B32A32Sscaled:
return 4 * scalarSize;
}
return scalarSize;
}
/// <summary>
/// Checks if the texture format is a depth or depth-stencil format.
/// </summary>

View File

@ -4,23 +4,22 @@ namespace Ryujinx.Graphics.GAL
{
public int FragmentOutputMap { get; }
public ResourceLayout ResourceLayout { get; }
public ComputeSize ComputeLocalSize { get; }
public ProgramPipelineState? State { get; }
public bool FromCache { get; set; }
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
public ShaderInfo(
int fragmentOutputMap,
ResourceLayout resourceLayout,
ComputeSize computeLocalSize,
ProgramPipelineState? state,
bool fromCache = false)
{
FragmentOutputMap = fragmentOutputMap;
ResourceLayout = resourceLayout;
ComputeLocalSize = computeLocalSize;
State = state;
FromCache = fromCache;
}
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
{
FragmentOutputMap = fragmentOutputMap;
ResourceLayout = resourceLayout;
State = null;
FromCache = fromCache;
}
}
}

View File

@ -11,8 +11,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
/// </summary>
class VtgAsComputeContext : IDisposable
{
private const int DummyBufferSize = 16;
private readonly GpuContext _context;
/// <summary>
@ -48,7 +46,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
1,
1,
1,
1,
format.GetBytesPerElement(),
format,
DepthStencilMode.Depth,
Target.TextureBuffer,
@ -521,21 +519,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write);
}
/// <summary>
/// Gets the range for a dummy 16 bytes buffer, filled with zeros.
/// </summary>
/// <returns>Dummy buffer range</returns>
public BufferRange GetDummyBufferRange()
{
if (_dummyBuffer == BufferHandle.Null)
{
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
}
return new BufferRange(_dummyBuffer, 0, DummyBufferSize);
}
/// <summary>
/// Gets the range for a sequential index buffer, with ever incrementing index values.
/// </summary>

View File

@ -147,7 +147,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
continue;
}
@ -163,15 +162,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
continue;
}
int vbStride = vertexBuffer.UnpackStride();
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
ulong oldVbSize = vbSize;
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
int componentSize = format.GetScalarSize();
@ -345,20 +341,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
return maxOutputVertices / verticesPerPrimitive;
}
/// <summary>
/// Binds a dummy buffer as vertex buffer into a buffer texture.
/// </summary>
/// <param name="reservations">Shader resource binding reservations</param>
/// <param name="index">Buffer texture index</param>
/// <param name="format">Buffer texture format</param>
private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format)
{
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(_vacContext.GetDummyBufferRange());
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
}
/// <summary>
/// Binds a vertex buffer into a buffer texture.
/// </summary>

View File

@ -324,6 +324,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
if (context.Capabilities.Api == TargetApi.Metal)
{
loadHostCache = false;
}
int programIndex = 0;
DataEntry entry = new();
@ -392,7 +397,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
context,
shaders,
specState.PipelineState,
specState.TransformFeedbackDescriptors != null);
specState.TransformFeedbackDescriptors != null,
specState.ComputeState.GetLocalSize());
IProgram hostProgram;
@ -629,8 +635,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return;
}
if (context.Capabilities.Api != TargetApi.Metal)
{
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
}
}
/// <summary>
/// Clears all content from the guest cache files.

View File

@ -490,7 +490,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
ShaderInfoBuilder shaderInfoBuilder = new(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null);
ref GpuChannelComputeState computeState = ref compilation.SpecializationState.ComputeState;
ShaderInfoBuilder shaderInfoBuilder = new(
_context,
compilation.SpecializationState.TransformFeedbackDescriptors != null,
computeLocalSize: computeState.GetLocalSize());
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
{

View File

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly GpuAccessorState _state;
private readonly int _stageIndex;
private readonly bool _compute;
private readonly bool _isVulkan;
private readonly bool _isOpenGL;
private readonly bool _hasGeometryShader;
private readonly bool _supportsQuads;
@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_channel = channel;
_state = state;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_isOpenGL = context.Capabilities.Api == TargetApi.OpenGL;
_hasGeometryShader = hasGeometryShader;
_supportsQuads = context.Capabilities.SupportsQuads;
@ -116,10 +116,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
public GpuGraphicsState QueryGraphicsState()
{
return _state.GraphicsState.CreateShaderGraphicsState(
!_isVulkan,
_isOpenGL,
_supportsQuads,
_hasGeometryShader,
_isVulkan || _state.GraphicsState.YNegateEnabled);
!_isOpenGL || _state.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>

View File

@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
}
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{
@ -103,7 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
}
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{

View File

@ -1,3 +1,5 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
@ -61,5 +63,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
SharedMemorySize = sharedMemorySize;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
}
/// <summary>
/// Gets the local group size of the shader in a GAL compatible struct.
/// </summary>
/// <returns>Local group size</returns>
public ComputeSize GetLocalSize()
{
return new ComputeSize(LocalSizeX, LocalSizeY, LocalSizeZ);
}
}
}

View File

@ -224,7 +224,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(
_context,
translatedShader.Program.Info,
computeState.GetLocalSize());
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
@ -425,7 +428,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
(program, ShaderProgramInfo vacInfo) = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
infoBuilder.AddStageInfoVac(vacInfo);
}
else
{
@ -530,7 +534,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
{
ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, context.GetVertexAsComputeInfo(), tfEnabled);
return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
}
@ -822,16 +826,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Creates shader translation options with the requested graphics API and flags.
/// The shader language is choosen based on the current configuration and graphics API.
/// The shader language is chosen based on the current configuration and graphics API.
/// </summary>
/// <param name="api">Target graphics API</param>
/// <param name="flags">Translation flags</param>
/// <returns>Translation options</returns>
private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
{
TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
? TargetLanguage.Spirv
: TargetLanguage.Glsl;
TargetLanguage lang = api switch
{
TargetApi.OpenGL => TargetLanguage.Glsl,
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
TargetApi.Metal => TargetLanguage.Msl,
};
return new TranslationOptions(lang, api, flags);
}

View File

@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceStages.Geometry;
private readonly GpuContext _context;
private readonly ComputeSize _computeLocalSize;
private int _fragmentOutputMap;
@ -39,9 +40,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false)
/// <param name="computeLocalSize">Indicates the local thread size for a compute shader</param>
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false, ComputeSize computeLocalSize = default)
{
_context = context;
_computeLocalSize = computeLocalSize;
_fragmentOutputMap = -1;
@ -95,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
{
AddDescriptor(stages, type, setIndex, start, count);
AddUsage(stages, type, setIndex, start, count, write);
// AddUsage(stages, type, setIndex, start, count, write);
}
/// <summary>
@ -159,6 +162,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddUsage(info.Images, stages, isImage: true);
}
public void AddStageInfoVac(ShaderProgramInfo info)
{
ResourceStages stages = info.Stage switch
{
ShaderStage.Compute => ResourceStages.Compute,
ShaderStage.Vertex => ResourceStages.Vertex,
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
ShaderStage.Geometry => ResourceStages.Geometry,
ShaderStage.Fragment => ResourceStages.Fragment,
_ => ResourceStages.None,
};
AddUsage(info.CBuffers, stages, isStorage: false);
AddUsage(info.SBuffers, stages, isStorage: true);
AddUsage(info.Textures, stages, isImage: false);
AddUsage(info.Images, stages, isImage: true);
}
/// <summary>
/// Adds a resource descriptor to the list of descriptors.
/// </summary>
@ -361,14 +383,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceLayout resourceLayout = new(descriptors.AsReadOnly(), usages.AsReadOnly());
if (pipeline.HasValue)
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
}
else
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
}
return new ShaderInfo(_fragmentOutputMap, resourceLayout, _computeLocalSize, pipeline, fromCache);
}
/// <summary>
@ -378,14 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="programs">Shaders from the disk cache</param>
/// <param name="pipeline">Optional pipeline for background compilation</param>
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
/// <param name="computeLocalSize">Compute local thread size</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCache(
GpuContext context,
IEnumerable<CachedShaderStage> programs,
ProgramPipelineState? pipeline,
bool tfEnabled)
bool tfEnabled,
ComputeSize computeLocalSize)
{
ShaderInfoBuilder builder = new(context, tfEnabled);
ShaderInfoBuilder builder = new(context, tfEnabled, computeLocalSize: computeLocalSize);
foreach (CachedShaderStage program in programs)
{
@ -403,11 +420,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="context">GPU context that owns the shader</param>
/// <param name="info">Compute shader information</param>
/// <param name="computeLocalSize">Compute local thread size</param>
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, ComputeSize computeLocalSize, bool fromCache = false)
{
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false);
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false, computeLocalSize: computeLocalSize);
builder.AddStageInfo(info);
@ -422,10 +440,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false)
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, ShaderProgramInfo info2, bool tfEnabled, bool fromCache = false)
{
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true, computeLocalSize: ComputeSize.VtgAsCompute);
builder.AddStageInfoVac(info2);
builder.AddStageInfo(info, vertexAsCompute: true);
return builder.Build(null, fromCache);

View File

@ -0,0 +1,146 @@
using System;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
interface IAuto
{
bool HasCommandBufferDependency(CommandBufferScoped cbs);
void IncrementReferenceCount();
void DecrementReferenceCount(int cbIndex);
void DecrementReferenceCount();
}
interface IAutoPrivate : IAuto
{
void AddCommandBufferDependencies(CommandBufferScoped cbs);
}
[SupportedOSPlatform("macos")]
class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
{
private int _referenceCount;
private T _value;
private readonly BitMap _cbOwnership;
private readonly MultiFenceHolder _waitable;
private bool _disposed;
private bool _destroyed;
public Auto(T value)
{
_referenceCount = 1;
_value = value;
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
}
public Auto(T value, MultiFenceHolder waitable) : this(value)
{
_waitable = waitable;
}
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
{
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
return Get(cbs);
}
public T GetUnsafe()
{
return _value;
}
public T Get(CommandBufferScoped cbs)
{
if (!_destroyed)
{
AddCommandBufferDependencies(cbs);
}
return _value;
}
public bool HasCommandBufferDependency(CommandBufferScoped cbs)
{
return _cbOwnership.IsSet(cbs.CommandBufferIndex);
}
public bool HasRentedCommandBufferDependency(CommandBufferPool cbp)
{
return _cbOwnership.AnySet();
}
public void AddCommandBufferDependencies(CommandBufferScoped cbs)
{
// We don't want to add a reference to this object to the command buffer
// more than once, so if we detect that the command buffer already has ownership
// of this object, then we can just return without doing anything else.
if (_cbOwnership.Set(cbs.CommandBufferIndex))
{
if (_waitable != null)
{
cbs.AddWaitable(_waitable);
}
cbs.AddDependant(this);
}
}
public bool TryIncrementReferenceCount()
{
int lastValue;
do
{
lastValue = _referenceCount;
if (lastValue == 0)
{
return false;
}
}
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
return true;
}
public void IncrementReferenceCount()
{
if (Interlocked.Increment(ref _referenceCount) == 1)
{
Interlocked.Decrement(ref _referenceCount);
throw new InvalidOperationException("Attempted to increment the reference count of an object that was already destroyed.");
}
}
public void DecrementReferenceCount(int cbIndex)
{
_cbOwnership.Clear(cbIndex);
DecrementReferenceCount();
}
public void DecrementReferenceCount()
{
if (Interlocked.Decrement(ref _referenceCount) == 0)
{
_value.Dispose();
_value = default;
_destroyed = true;
}
Debug.Assert(_referenceCount >= 0);
}
public void Dispose()
{
if (!_disposed)
{
DecrementReferenceCount();
_disposed = true;
}
}
}
}

View File

@ -0,0 +1,107 @@
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class BackgroundResource : IDisposable
{
private readonly MetalRenderer _renderer;
private CommandBufferPool _pool;
private PersistentFlushBuffer _flushBuffer;
public BackgroundResource(MetalRenderer renderer)
{
_renderer = renderer;
}
public CommandBufferPool GetPool()
{
if (_pool == null)
{
MTLCommandQueue queue = _renderer.BackgroundQueue;
_pool = new CommandBufferPool(queue, true);
_pool.Initialize(null); // TODO: Proper encoder factory for background render/compute
}
return _pool;
}
public PersistentFlushBuffer GetFlushBuffer()
{
_flushBuffer ??= new PersistentFlushBuffer(_renderer);
return _flushBuffer;
}
public void Dispose()
{
_pool?.Dispose();
_flushBuffer?.Dispose();
}
}
[SupportedOSPlatform("macos")]
class BackgroundResources : IDisposable
{
private readonly MetalRenderer _renderer;
private readonly Dictionary<Thread, BackgroundResource> _resources;
public BackgroundResources(MetalRenderer renderer)
{
_renderer = renderer;
_resources = new Dictionary<Thread, BackgroundResource>();
}
private void Cleanup()
{
lock (_resources)
{
foreach (KeyValuePair<Thread, BackgroundResource> tuple in _resources)
{
if (!tuple.Key.IsAlive)
{
tuple.Value.Dispose();
_resources.Remove(tuple.Key);
}
}
}
}
public BackgroundResource Get()
{
Thread thread = Thread.CurrentThread;
lock (_resources)
{
if (!_resources.TryGetValue(thread, out BackgroundResource resource))
{
Cleanup();
resource = new BackgroundResource(_renderer);
_resources[thread] = resource;
}
return resource;
}
}
public void Dispose()
{
lock (_resources)
{
foreach (var resource in _resources.Values)
{
resource.Dispose();
}
}
}
}
}

View File

@ -0,0 +1,157 @@
namespace Ryujinx.Graphics.Metal
{
readonly struct BitMap
{
public const int IntSize = 64;
private const int IntShift = 6;
private const int IntMask = IntSize - 1;
private readonly long[] _masks;
public BitMap(int count)
{
_masks = new long[(count + IntMask) / IntSize];
}
public bool AnySet()
{
for (int i = 0; i < _masks.Length; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
return false;
}
public bool IsSet(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
return (_masks[wordIndex] & wordMask) != 0;
}
public bool IsSet(int start, int end)
{
if (start == end)
{
return IsSet(start);
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
return (_masks[startIndex] & startMask & endMask) != 0;
}
if ((_masks[startIndex] & startMask) != 0)
{
return true;
}
for (int i = startIndex + 1; i < endIndex; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
if ((_masks[endIndex] & endMask) != 0)
{
return true;
}
return false;
}
public bool Set(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
if ((_masks[wordIndex] & wordMask) != 0)
{
return false;
}
_masks[wordIndex] |= wordMask;
return true;
}
public void SetRange(int start, int end)
{
if (start == end)
{
Set(start);
return;
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
_masks[startIndex] |= startMask & endMask;
}
else
{
_masks[startIndex] |= startMask;
for (int i = startIndex + 1; i < endIndex; i++)
{
_masks[i] |= -1;
}
_masks[endIndex] |= endMask;
}
}
public void Clear(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
_masks[wordIndex] &= ~wordMask;
}
public void Clear()
{
for (int i = 0; i < _masks.Length; i++)
{
_masks[i] = 0;
}
}
public void ClearInt(int start, int end)
{
for (int i = start; i <= end; i++)
{
_masks[i] = 0;
}
}
}
}

View File

@ -0,0 +1,385 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class BufferHolder : IDisposable
{
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
public int Size { get; }
private readonly IntPtr _map;
private readonly MetalRenderer _renderer;
private readonly Pipeline _pipeline;
private readonly MultiFenceHolder _waitable;
private readonly Auto<DisposableBuffer> _buffer;
private readonly ReaderWriterLockSlim _flushLock;
private FenceHolder _flushFence;
private int _flushWaiting;
public BufferHolder(MetalRenderer renderer, Pipeline pipeline, MTLBuffer buffer, int size)
{
_renderer = renderer;
_pipeline = pipeline;
_map = buffer.Contents;
_waitable = new MultiFenceHolder(size);
_buffer = new Auto<DisposableBuffer>(new(buffer), _waitable);
_flushLock = new ReaderWriterLockSlim();
Size = size;
}
public Auto<DisposableBuffer> GetBuffer()
{
return _buffer;
}
public Auto<DisposableBuffer> GetBuffer(bool isWrite)
{
if (isWrite)
{
SignalWrite(0, Size);
}
return _buffer;
}
public Auto<DisposableBuffer> GetBuffer(int offset, int size, bool isWrite)
{
if (isWrite)
{
SignalWrite(offset, size);
}
return _buffer;
}
public void SignalWrite(int offset, int size)
{
if (offset == 0 && size == Size)
{
_cachedConvertedBuffers.Clear();
}
else
{
_cachedConvertedBuffers.ClearRange(offset, size);
}
}
private void ClearFlushFence()
{
// Assumes _flushLock is held as writer.
if (_flushFence != null)
{
if (_flushWaiting == 0)
{
_flushFence.Put();
}
_flushFence = null;
}
}
private void WaitForFlushFence()
{
if (_flushFence == null)
{
return;
}
// If storage has changed, make sure the fence has been reached so that the data is in place.
_flushLock.ExitReadLock();
_flushLock.EnterWriteLock();
if (_flushFence != null)
{
var fence = _flushFence;
Interlocked.Increment(ref _flushWaiting);
// Don't wait in the lock.
_flushLock.ExitWriteLock();
fence.Wait();
_flushLock.EnterWriteLock();
if (Interlocked.Decrement(ref _flushWaiting) == 0)
{
fence.Put();
}
_flushFence = null;
}
// Assumes the _flushLock is held as reader, returns in same state.
_flushLock.ExitWriteLock();
_flushLock.EnterReadLock();
}
public PinnedSpan<byte> GetData(int offset, int size)
{
_flushLock.EnterReadLock();
WaitForFlushFence();
Span<byte> result;
if (_map != IntPtr.Zero)
{
result = GetDataStorage(offset, size);
// Need to be careful here, the buffer can't be unmapped while the data is being used.
_buffer.IncrementReferenceCount();
_flushLock.ExitReadLock();
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
}
throw new InvalidOperationException("The buffer is not mapped");
}
public unsafe Span<byte> GetDataStorage(int offset, int size)
{
int mappingSize = Math.Min(size, Size - offset);
if (_map != IntPtr.Zero)
{
return new Span<byte>((void*)(_map + offset), mappingSize);
}
throw new InvalidOperationException("The buffer is not mapped.");
}
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, bool allowCbsWait = true)
{
int dataSize = Math.Min(data.Length, Size - offset);
if (dataSize == 0)
{
return;
}
if (_map != IntPtr.Zero)
{
// If persistently mapped, set the data directly if the buffer is not currently in use.
bool isRented = _buffer.HasRentedCommandBufferDependency(_renderer.CommandBufferPool);
// If the buffer is rented, take a little more time and check if the use overlaps this handle.
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false);
if (!needsFlush)
{
WaitForFences(offset, dataSize);
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
SignalWrite(offset, dataSize);
return;
}
}
if (cbs != null &&
cbs.Value.Encoders.CurrentEncoderType == EncoderType.Render &&
!(_buffer.HasCommandBufferDependency(cbs.Value) &&
_waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
{
// If the buffer hasn't been used on the command buffer yet, try to preload the data.
// This avoids ending and beginning render passes on each buffer data upload.
cbs = _pipeline.GetPreloadCommandBuffer();
}
if (allowCbsWait)
{
_renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, this, offset, data);
}
else
{
bool rentCbs = cbs == null;
if (rentCbs)
{
cbs = _renderer.CommandBufferPool.Rent();
}
if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, this, offset, data))
{
// Need to do a slow upload.
BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize);
srcHolder.SetDataUnchecked(0, data);
var srcBuffer = srcHolder.GetBuffer();
var dstBuffer = this.GetBuffer(true);
Copy(cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
srcHolder.Dispose();
}
if (rentCbs)
{
cbs.Value.Dispose();
}
}
}
public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
{
int dataSize = Math.Min(data.Length, Size - offset);
if (dataSize == 0)
{
return;
}
if (_map != IntPtr.Zero)
{
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
}
}
public void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
{
SetDataUnchecked(offset, MemoryMarshal.AsBytes(data));
}
public static void Copy(
CommandBufferScoped cbs,
Auto<DisposableBuffer> src,
Auto<DisposableBuffer> dst,
int srcOffset,
int dstOffset,
int size,
bool registerSrcUsage = true)
{
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value;
cbs.Encoders.EnsureBlitEncoder().CopyFromBuffer(
srcBuffer,
(ulong)srcOffset,
dstbuffer,
(ulong)dstOffset,
(ulong)size);
}
public void WaitForFences()
{
_waitable.WaitForFences();
}
public void WaitForFences(int offset, int size)
{
_waitable.WaitForFences(offset, size);
}
private bool BoundToRange(int offset, ref int size)
{
if (offset >= Size)
{
return false;
}
size = Math.Min(Size - offset, size);
return true;
}
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
{
if (!BoundToRange(offset, ref size))
{
return null;
}
var key = new I8ToI16CacheKey(_renderer);
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
{
holder = _renderer.BufferManager.Create((size * 2 + 3) & ~3);
_renderer.HelperShader.ConvertI8ToI16(cbs, this, holder, offset, size);
key.SetBuffer(holder.GetBuffer());
_cachedConvertedBuffers.Add(offset, size, key, holder);
}
return holder.GetBuffer();
}
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
{
if (!BoundToRange(offset, ref size))
{
return null;
}
var key = new TopologyConversionCacheKey(_renderer, pattern, indexSize);
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
{
// The destination index size is always I32.
int indexCount = size / indexSize;
int convertedCount = pattern.GetConvertedCount(indexCount);
holder = _renderer.BufferManager.Create(convertedCount * 4);
_renderer.HelperShader.ConvertIndexBuffer(cbs, this, holder, pattern, indexSize, offset, indexCount);
key.SetBuffer(holder.GetBuffer());
_cachedConvertedBuffers.Add(offset, size, key, holder);
}
return holder.GetBuffer();
}
public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
{
return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
}
public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
{
_cachedConvertedBuffers.Add(offset, size, key, holder);
}
public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
{
_cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
}
public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
{
_cachedConvertedBuffers.Remove(offset, size, key);
}
public void Dispose()
{
_pipeline.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
_buffer.Dispose();
_cachedConvertedBuffers.Dispose();
_flushLock.EnterWriteLock();
ClearFlushFence();
_flushLock.ExitWriteLock();
}
}
}

View File

@ -0,0 +1,237 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly struct ScopedTemporaryBuffer : IDisposable
{
private readonly BufferManager _bufferManager;
private readonly bool _isReserved;
public readonly BufferRange Range;
public readonly BufferHolder Holder;
public BufferHandle Handle => Range.Handle;
public int Offset => Range.Offset;
public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved)
{
_bufferManager = bufferManager;
Range = new BufferRange(handle, offset, size);
Holder = holder;
_isReserved = isReserved;
}
public void Dispose()
{
if (!_isReserved)
{
_bufferManager.Delete(Range.Handle);
}
}
}
[SupportedOSPlatform("macos")]
class BufferManager : IDisposable
{
private readonly IdList<BufferHolder> _buffers;
private readonly MTLDevice _device;
private readonly MetalRenderer _renderer;
private readonly Pipeline _pipeline;
public int BufferCount { get; private set; }
public StagingBuffer StagingBuffer { get; }
public BufferManager(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
{
_device = device;
_renderer = renderer;
_pipeline = pipeline;
_buffers = new IdList<BufferHolder>();
StagingBuffer = new StagingBuffer(_renderer, this);
}
public BufferHandle Create(nint pointer, int size)
{
// TODO: This is the wrong Metal method, we need no-copy which SharpMetal isn't giving us.
var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
if (buffer == IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}, and pointer 0x{pointer:X}.");
return BufferHandle.Null;
}
var holder = new BufferHolder(_renderer, _pipeline, buffer, size);
BufferCount++;
ulong handle64 = (uint)_buffers.Add(holder);
return Unsafe.As<ulong, BufferHandle>(ref handle64);
}
public BufferHandle CreateWithHandle(int size)
{
return CreateWithHandle(size, out _);
}
public BufferHandle CreateWithHandle(int size, out BufferHolder holder)
{
holder = Create(size);
if (holder == null)
{
return BufferHandle.Null;
}
BufferCount++;
ulong handle64 = (uint)_buffers.Add(holder);
return Unsafe.As<ulong, BufferHandle>(ref handle64);
}
public ScopedTemporaryBuffer ReserveOrCreate(CommandBufferScoped cbs, int size)
{
StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size);
if (result.HasValue)
{
return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true);
}
else
{
// Create a temporary buffer.
BufferHandle handle = CreateWithHandle(size, out BufferHolder holder);
return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false);
}
}
public BufferHolder Create(int size)
{
var buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared);
if (buffer != IntPtr.Zero)
{
return new BufferHolder(_renderer, _pipeline, buffer, size);
}
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}.");
return null;
}
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, bool isWrite, out int size)
{
if (TryGetBuffer(handle, out var holder))
{
size = holder.Size;
return holder.GetBuffer(isWrite);
}
size = 0;
return null;
}
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, int offset, int size, bool isWrite)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetBuffer(offset, size, isWrite);
}
return null;
}
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, bool isWrite)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetBuffer(isWrite);
}
return null;
}
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetBufferI8ToI16(cbs, offset, size);
}
return null;
}
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize);
}
return null;
}
public PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetData(offset, size);
}
return new PinnedSpan<byte>();
}
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
{
SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null);
}
public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs)
{
if (TryGetBuffer(handle, out var holder))
{
holder.SetData(offset, data, cbs);
}
}
public void Delete(BufferHandle handle)
{
if (TryGetBuffer(handle, out var holder))
{
holder.Dispose();
_buffers.Remove((int)Unsafe.As<BufferHandle, ulong>(ref handle));
}
}
private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder)
{
return _buffers.TryGetValue((int)Unsafe.As<BufferHandle, ulong>(ref handle), out holder);
}
public void Dispose()
{
StagingBuffer.Dispose();
foreach (var buffer in _buffers)
{
buffer.Dispose();
}
}
}
}

View File

@ -0,0 +1,85 @@
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class BufferUsageBitmap
{
private readonly BitMap _bitmap;
private readonly int _size;
private readonly int _granularity;
private readonly int _bits;
private readonly int _writeBitOffset;
private readonly int _intsPerCb;
private readonly int _bitsPerCb;
public BufferUsageBitmap(int size, int granularity)
{
_size = size;
_granularity = granularity;
// There are two sets of bits - one for read tracking, and the other for write.
int bits = (size + (granularity - 1)) / granularity;
_writeBitOffset = bits;
_bits = bits << 1;
_intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize;
_bitsPerCb = _intsPerCb * BitMap.IntSize;
_bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
}
public void Add(int cbIndex, int offset, int size, bool write)
{
if (size == 0)
{
return;
}
// Some usages can be out of bounds (vertex buffer on amd), so bound if necessary.
if (offset + size > _size)
{
size = _size - offset;
}
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
int start = cbBase + offset / _granularity;
int end = cbBase + (offset + size - 1) / _granularity;
_bitmap.SetRange(start, end);
}
public bool OverlapsWith(int cbIndex, int offset, int size, bool write = false)
{
if (size == 0)
{
return false;
}
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
int start = cbBase + offset / _granularity;
int end = cbBase + (offset + size - 1) / _granularity;
return _bitmap.IsSet(start, end);
}
public bool OverlapsWith(int offset, int size, bool write)
{
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
{
if (OverlapsWith(i, offset, size, write))
{
return true;
}
}
return false;
}
public void Clear(int cbIndex)
{
_bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1);
}
}
}

View File

@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
interface ICacheKey : IDisposable
{
bool KeyEqual(ICacheKey other);
}
[SupportedOSPlatform("macos")]
struct I8ToI16CacheKey : ICacheKey
{
// Used to notify the pipeline that bindings have invalidated on dispose.
// private readonly MetalRenderer _renderer;
// private Auto<DisposableBuffer> _buffer;
public I8ToI16CacheKey(MetalRenderer renderer)
{
// _renderer = renderer;
// _buffer = null;
}
public readonly bool KeyEqual(ICacheKey other)
{
return other is I8ToI16CacheKey;
}
public readonly void SetBuffer(Auto<DisposableBuffer> buffer)
{
// _buffer = buffer;
}
public readonly void Dispose()
{
// TODO: Tell pipeline buffer is dirty!
// _renderer.PipelineInternal.DirtyIndexBuffer(_buffer);
}
}
[SupportedOSPlatform("macos")]
readonly struct TopologyConversionCacheKey : ICacheKey
{
private readonly IndexBufferPattern _pattern;
private readonly int _indexSize;
// Used to notify the pipeline that bindings have invalidated on dispose.
// private readonly MetalRenderer _renderer;
// private Auto<DisposableBuffer> _buffer;
public TopologyConversionCacheKey(MetalRenderer renderer, IndexBufferPattern pattern, int indexSize)
{
// _renderer = renderer;
// _buffer = null;
_pattern = pattern;
_indexSize = indexSize;
}
public readonly bool KeyEqual(ICacheKey other)
{
return other is TopologyConversionCacheKey entry &&
entry._pattern == _pattern &&
entry._indexSize == _indexSize;
}
public void SetBuffer(Auto<DisposableBuffer> buffer)
{
// _buffer = buffer;
}
public readonly void Dispose()
{
// TODO: Tell pipeline buffer is dirty!
// _renderer.PipelineInternal.DirtyVertexBuffer(_buffer);
}
}
[SupportedOSPlatform("macos")]
readonly struct Dependency
{
private readonly BufferHolder _buffer;
private readonly int _offset;
private readonly int _size;
private readonly ICacheKey _key;
public Dependency(BufferHolder buffer, int offset, int size, ICacheKey key)
{
_buffer = buffer;
_offset = offset;
_size = size;
_key = key;
}
public void RemoveFromOwner()
{
_buffer.RemoveCachedConvertedBuffer(_offset, _size, _key);
}
}
[SupportedOSPlatform("macos")]
struct CacheByRange<T> where T : IDisposable
{
private struct Entry
{
public readonly ICacheKey Key;
public readonly T Value;
public List<Dependency> DependencyList;
public Entry(ICacheKey key, T value)
{
Key = key;
Value = value;
DependencyList = null;
}
public readonly void InvalidateDependencies()
{
if (DependencyList != null)
{
foreach (Dependency dependency in DependencyList)
{
dependency.RemoveFromOwner();
}
DependencyList.Clear();
}
}
}
private Dictionary<ulong, List<Entry>> _ranges;
public void Add(int offset, int size, ICacheKey key, T value)
{
List<Entry> entries = GetEntries(offset, size);
entries.Add(new Entry(key, value));
}
public void AddDependency(int offset, int size, ICacheKey key, Dependency dependency)
{
List<Entry> entries = GetEntries(offset, size);
for (int i = 0; i < entries.Count; i++)
{
Entry entry = entries[i];
if (entry.Key.KeyEqual(key))
{
if (entry.DependencyList == null)
{
entry.DependencyList = new List<Dependency>();
entries[i] = entry;
}
entry.DependencyList.Add(dependency);
break;
}
}
}
public void Remove(int offset, int size, ICacheKey key)
{
List<Entry> entries = GetEntries(offset, size);
for (int i = 0; i < entries.Count; i++)
{
Entry entry = entries[i];
if (entry.Key.KeyEqual(key))
{
entries.RemoveAt(i--);
DestroyEntry(entry);
}
}
if (entries.Count == 0)
{
_ranges.Remove(PackRange(offset, size));
}
}
public bool TryGetValue(int offset, int size, ICacheKey key, out T value)
{
List<Entry> entries = GetEntries(offset, size);
foreach (Entry entry in entries)
{
if (entry.Key.KeyEqual(key))
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
public void Clear()
{
if (_ranges != null)
{
foreach (List<Entry> entries in _ranges.Values)
{
foreach (Entry entry in entries)
{
DestroyEntry(entry);
}
}
_ranges.Clear();
_ranges = null;
}
}
public readonly void ClearRange(int offset, int size)
{
if (_ranges != null && _ranges.Count > 0)
{
int end = offset + size;
List<ulong> toRemove = null;
foreach (KeyValuePair<ulong, List<Entry>> range in _ranges)
{
(int rOffset, int rSize) = UnpackRange(range.Key);
int rEnd = rOffset + rSize;
if (rEnd > offset && rOffset < end)
{
List<Entry> entries = range.Value;
foreach (Entry entry in entries)
{
DestroyEntry(entry);
}
(toRemove ??= new List<ulong>()).Add(range.Key);
}
}
if (toRemove != null)
{
foreach (ulong range in toRemove)
{
_ranges.Remove(range);
}
}
}
}
private List<Entry> GetEntries(int offset, int size)
{
_ranges ??= new Dictionary<ulong, List<Entry>>();
ulong key = PackRange(offset, size);
if (!_ranges.TryGetValue(key, out List<Entry> value))
{
value = new List<Entry>();
_ranges.Add(key, value);
}
return value;
}
private static void DestroyEntry(Entry entry)
{
entry.Key.Dispose();
entry.Value?.Dispose();
entry.InvalidateDependencies();
}
private static ulong PackRange(int offset, int size)
{
return (uint)offset | ((ulong)size << 32);
}
private static (int offset, int size) UnpackRange(ulong range)
{
return ((int)range, (int)(range >> 32));
}
public void Dispose()
{
Clear();
}
}
}

View File

@ -0,0 +1,170 @@
using Ryujinx.Graphics.Metal;
using SharpMetal.Metal;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
interface IEncoderFactory
{
MTLRenderCommandEncoder CreateRenderCommandEncoder();
MTLComputeCommandEncoder CreateComputeCommandEncoder();
}
/// <summary>
/// Tracks active encoder object for a command buffer.
/// </summary>
[SupportedOSPlatform("macos")]
class CommandBufferEncoder
{
public EncoderType CurrentEncoderType { get; private set; } = EncoderType.None;
public MTLBlitCommandEncoder BlitEncoder => new(CurrentEncoder.Value);
public MTLComputeCommandEncoder ComputeEncoder => new(CurrentEncoder.Value);
public MTLRenderCommandEncoder RenderEncoder => new(CurrentEncoder.Value);
internal MTLCommandEncoder? CurrentEncoder { get; private set; }
private MTLCommandBuffer _commandBuffer;
private IEncoderFactory _encoderFactory;
public void Initialize(MTLCommandBuffer commandBuffer, IEncoderFactory encoderFactory)
{
_commandBuffer = commandBuffer;
_encoderFactory = encoderFactory;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MTLRenderCommandEncoder EnsureRenderEncoder()
{
if (CurrentEncoderType != EncoderType.Render)
{
return BeginRenderPass();
}
return RenderEncoder;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MTLBlitCommandEncoder EnsureBlitEncoder()
{
if (CurrentEncoderType != EncoderType.Blit)
{
return BeginBlitPass();
}
return BlitEncoder;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MTLComputeCommandEncoder EnsureComputeEncoder()
{
if (CurrentEncoderType != EncoderType.Compute)
{
return BeginComputePass();
}
return ComputeEncoder;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetRenderEncoder(out MTLRenderCommandEncoder encoder)
{
if (CurrentEncoderType != EncoderType.Render)
{
encoder = default;
return false;
}
encoder = RenderEncoder;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetBlitEncoder(out MTLBlitCommandEncoder encoder)
{
if (CurrentEncoderType != EncoderType.Blit)
{
encoder = default;
return false;
}
encoder = BlitEncoder;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetComputeEncoder(out MTLComputeCommandEncoder encoder)
{
if (CurrentEncoderType != EncoderType.Compute)
{
encoder = default;
return false;
}
encoder = ComputeEncoder;
return true;
}
public void EndCurrentPass()
{
if (CurrentEncoder != null)
{
switch (CurrentEncoderType)
{
case EncoderType.Blit:
BlitEncoder.EndEncoding();
CurrentEncoder = null;
break;
case EncoderType.Compute:
ComputeEncoder.EndEncoding();
CurrentEncoder = null;
break;
case EncoderType.Render:
RenderEncoder.EndEncoding();
CurrentEncoder = null;
break;
default:
throw new InvalidOperationException();
}
CurrentEncoderType = EncoderType.None;
}
}
private MTLRenderCommandEncoder BeginRenderPass()
{
EndCurrentPass();
var renderCommandEncoder = _encoderFactory.CreateRenderCommandEncoder();
CurrentEncoder = renderCommandEncoder;
CurrentEncoderType = EncoderType.Render;
return renderCommandEncoder;
}
private MTLBlitCommandEncoder BeginBlitPass()
{
EndCurrentPass();
using var descriptor = new MTLBlitPassDescriptor();
var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor);
CurrentEncoder = blitCommandEncoder;
CurrentEncoderType = EncoderType.Blit;
return blitCommandEncoder;
}
private MTLComputeCommandEncoder BeginComputePass()
{
EndCurrentPass();
var computeCommandEncoder = _encoderFactory.CreateComputeCommandEncoder();
CurrentEncoder = computeCommandEncoder;
CurrentEncoderType = EncoderType.Compute;
return computeCommandEncoder;
}
}

View File

@ -0,0 +1,289 @@
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class CommandBufferPool : IDisposable
{
public const int MaxCommandBuffers = 16;
private readonly int _totalCommandBuffers;
private readonly int _totalCommandBuffersMask;
private readonly MTLCommandQueue _queue;
private readonly Thread _owner;
private IEncoderFactory _defaultEncoderFactory;
public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
[SupportedOSPlatform("macos")]
private struct ReservedCommandBuffer
{
public bool InUse;
public bool InConsumption;
public int SubmissionCount;
public MTLCommandBuffer CommandBuffer;
public CommandBufferEncoder Encoders;
public FenceHolder Fence;
public List<IAuto> Dependants;
public List<MultiFenceHolder> Waitables;
public void Use(MTLCommandQueue queue, IEncoderFactory stateManager)
{
MTLCommandBufferDescriptor descriptor = new();
#if DEBUG
descriptor.ErrorOptions = MTLCommandBufferErrorOption.EncoderExecutionStatus;
#endif
CommandBuffer = queue.CommandBuffer(descriptor);
Fence = new FenceHolder(CommandBuffer);
Encoders.Initialize(CommandBuffer, stateManager);
InUse = true;
}
public void Initialize()
{
Dependants = new List<IAuto>();
Waitables = new List<MultiFenceHolder>();
Encoders = new CommandBufferEncoder();
}
}
private readonly ReservedCommandBuffer[] _commandBuffers;
private readonly int[] _queuedIndexes;
private int _queuedIndexesPtr;
private int _queuedCount;
private int _inUseCount;
public CommandBufferPool(MTLCommandQueue queue, bool isLight = false)
{
_queue = queue;
_owner = Thread.CurrentThread;
_totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
_totalCommandBuffersMask = _totalCommandBuffers - 1;
_commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
_queuedIndexes = new int[_totalCommandBuffers];
_queuedIndexesPtr = 0;
_queuedCount = 0;
}
public void Initialize(IEncoderFactory encoderFactory)
{
_defaultEncoderFactory = encoderFactory;
for (int i = 0; i < _totalCommandBuffers; i++)
{
_commandBuffers[i].Initialize();
WaitAndDecrementRef(i);
}
}
public void AddDependant(int cbIndex, IAuto dependant)
{
dependant.IncrementReferenceCount();
_commandBuffers[cbIndex].Dependants.Add(dependant);
}
public void AddWaitable(MultiFenceHolder waitable)
{
lock (_commandBuffers)
{
for (int i = 0; i < _totalCommandBuffers; i++)
{
ref var entry = ref _commandBuffers[i];
if (entry.InConsumption)
{
AddWaitable(i, waitable);
}
}
}
}
public void AddInUseWaitable(MultiFenceHolder waitable)
{
lock (_commandBuffers)
{
for (int i = 0; i < _totalCommandBuffers; i++)
{
ref var entry = ref _commandBuffers[i];
if (entry.InUse)
{
AddWaitable(i, waitable);
}
}
}
}
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
{
ref var entry = ref _commandBuffers[cbIndex];
if (waitable.AddFence(cbIndex, entry.Fence))
{
entry.Waitables.Add(waitable);
}
}
public bool IsFenceOnRentedCommandBuffer(FenceHolder fence)
{
lock (_commandBuffers)
{
for (int i = 0; i < _totalCommandBuffers; i++)
{
ref var entry = ref _commandBuffers[i];
if (entry.InUse && entry.Fence == fence)
{
return true;
}
}
}
return false;
}
public FenceHolder GetFence(int cbIndex)
{
return _commandBuffers[cbIndex].Fence;
}
public int GetSubmissionCount(int cbIndex)
{
return _commandBuffers[cbIndex].SubmissionCount;
}
private int FreeConsumed(bool wait)
{
int freeEntry = 0;
while (_queuedCount > 0)
{
int index = _queuedIndexes[_queuedIndexesPtr];
ref var entry = ref _commandBuffers[index];
if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
{
WaitAndDecrementRef(index);
wait = false;
freeEntry = index;
_queuedCount--;
_queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers;
}
else
{
break;
}
}
return freeEntry;
}
public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
{
Return(cbs);
return Rent();
}
public CommandBufferScoped Rent()
{
lock (_commandBuffers)
{
int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers);
for (int i = 0; i < _totalCommandBuffers; i++)
{
ref var entry = ref _commandBuffers[cursor];
if (!entry.InUse && !entry.InConsumption)
{
entry.Use(_queue, _defaultEncoderFactory);
_inUseCount++;
return new CommandBufferScoped(this, entry.CommandBuffer, entry.Encoders, cursor);
}
cursor = (cursor + 1) & _totalCommandBuffersMask;
}
}
throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
}
public void Return(CommandBufferScoped cbs)
{
// Ensure the encoder is committed.
cbs.Encoders.EndCurrentPass();
lock (_commandBuffers)
{
int cbIndex = cbs.CommandBufferIndex;
ref var entry = ref _commandBuffers[cbIndex];
Debug.Assert(entry.InUse);
Debug.Assert(entry.CommandBuffer.NativePtr == cbs.CommandBuffer.NativePtr);
entry.InUse = false;
entry.InConsumption = true;
entry.SubmissionCount++;
_inUseCount--;
var commandBuffer = entry.CommandBuffer;
commandBuffer.Commit();
int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
_queuedIndexes[ptr] = cbIndex;
_queuedCount++;
}
}
private void WaitAndDecrementRef(int cbIndex)
{
ref var entry = ref _commandBuffers[cbIndex];
if (entry.InConsumption)
{
entry.Fence.Wait();
entry.InConsumption = false;
}
foreach (var dependant in entry.Dependants)
{
dependant.DecrementReferenceCount(cbIndex);
}
foreach (var waitable in entry.Waitables)
{
waitable.RemoveFence(cbIndex);
waitable.RemoveBufferUses(cbIndex);
}
entry.Dependants.Clear();
entry.Waitables.Clear();
entry.Fence?.Dispose();
}
public void Dispose()
{
for (int i = 0; i < _totalCommandBuffers; i++)
{
WaitAndDecrementRef(i);
}
}
}
}

View File

@ -0,0 +1,43 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly struct CommandBufferScoped : IDisposable
{
private readonly CommandBufferPool _pool;
public MTLCommandBuffer CommandBuffer { get; }
public CommandBufferEncoder Encoders { get; }
public int CommandBufferIndex { get; }
public CommandBufferScoped(CommandBufferPool pool, MTLCommandBuffer commandBuffer, CommandBufferEncoder encoders, int commandBufferIndex)
{
_pool = pool;
CommandBuffer = commandBuffer;
Encoders = encoders;
CommandBufferIndex = commandBufferIndex;
}
public void AddDependant(IAuto dependant)
{
_pool.AddDependant(CommandBufferIndex, dependant);
}
public void AddWaitable(MultiFenceHolder waitable)
{
_pool.AddWaitable(CommandBufferIndex, waitable);
}
public FenceHolder GetFence()
{
return _pool.GetFence(CommandBufferIndex);
}
public void Dispose()
{
_pool?.Return(this);
}
}
}

View File

@ -0,0 +1,41 @@
namespace Ryujinx.Graphics.Metal
{
static class Constants
{
public const int MaxShaderStages = 5;
public const int MaxVertexBuffers = 16;
public const int MaxUniformBuffersPerStage = 18;
public const int MaxStorageBuffersPerStage = 16;
public const int MaxTexturesPerStage = 64;
public const int MaxImagesPerStage = 16;
public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
public const int MaxColorAttachments = 8;
public const int MaxViewports = 16;
// TODO: Check this value
public const int MaxVertexAttributes = 31;
public const int MinResourceAlignment = 16;
// Must match constants set in shader generation
public const uint ZeroBufferIndex = MaxVertexBuffers;
public const uint BaseSetIndex = MaxVertexBuffers + 1;
public const uint ConstantBuffersIndex = BaseSetIndex;
public const uint StorageBuffersIndex = BaseSetIndex + 1;
public const uint TexturesIndex = BaseSetIndex + 2;
public const uint ImagesIndex = BaseSetIndex + 3;
public const uint ConstantBuffersSetIndex = 0;
public const uint StorageBuffersSetIndex = 1;
public const uint TexturesSetIndex = 2;
public const uint ImagesSetIndex = 3;
public const uint MaximumBufferArgumentTableEntries = 31;
public const uint MaximumExtraSets = MaximumBufferArgumentTableEntries - ImagesIndex;
}
}

View File

@ -0,0 +1,22 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Metal
{
class CounterEvent : ICounterEvent
{
public CounterEvent()
{
Invalid = false;
}
public bool Invalid { get; set; }
public bool ReserveForHostAccess()
{
return true;
}
public void Flush() { }
public void Dispose() { }
}
}

View File

@ -0,0 +1,68 @@
using Ryujinx.Graphics.Metal.State;
using SharpMetal.Metal;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class DepthStencilCache : StateCache<MTLDepthStencilState, DepthStencilUid, DepthStencilUid>
{
private readonly MTLDevice _device;
public DepthStencilCache(MTLDevice device)
{
_device = device;
}
protected override DepthStencilUid GetHash(DepthStencilUid descriptor)
{
return descriptor;
}
protected override MTLDepthStencilState CreateValue(DepthStencilUid descriptor)
{
// Create descriptors
ref StencilUid frontUid = ref descriptor.FrontFace;
using var frontFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = frontUid.StencilFailureOperation,
DepthFailureOperation = frontUid.DepthFailureOperation,
DepthStencilPassOperation = frontUid.DepthStencilPassOperation,
StencilCompareFunction = frontUid.StencilCompareFunction,
ReadMask = frontUid.ReadMask,
WriteMask = frontUid.WriteMask
};
ref StencilUid backUid = ref descriptor.BackFace;
using var backFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = backUid.StencilFailureOperation,
DepthFailureOperation = backUid.DepthFailureOperation,
DepthStencilPassOperation = backUid.DepthStencilPassOperation,
StencilCompareFunction = backUid.StencilCompareFunction,
ReadMask = backUid.ReadMask,
WriteMask = backUid.WriteMask
};
var mtlDescriptor = new MTLDepthStencilDescriptor
{
DepthCompareFunction = descriptor.DepthCompareFunction,
DepthWriteEnabled = descriptor.DepthWriteEnabled
};
if (descriptor.StencilTestEnabled)
{
mtlDescriptor.BackFaceStencil = backFaceStencil;
mtlDescriptor.FrontFaceStencil = frontFaceStencil;
}
using (mtlDescriptor)
{
return _device.NewDepthStencilState(mtlDescriptor);
}
}
}
}

View File

@ -0,0 +1,26 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly struct DisposableBuffer : IDisposable
{
public MTLBuffer Value { get; }
public DisposableBuffer(MTLBuffer buffer)
{
Value = buffer;
}
public void Dispose()
{
if (Value != IntPtr.Zero)
{
Value.SetPurgeableState(MTLPurgeableState.Empty);
Value.Dispose();
}
}
}
}

View File

@ -0,0 +1,22 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly struct DisposableSampler : IDisposable
{
public MTLSamplerState Value { get; }
public DisposableSampler(MTLSamplerState sampler)
{
Value = sampler;
}
public void Dispose()
{
Value.Dispose();
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Graphics.Metal.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
Texture Run(Texture view, int width, int height);
}
}

View File

@ -0,0 +1,18 @@
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.Metal.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
Texture view,
Texture destinationTexture,
Format format,
int width,
int height,
Extents2D source,
Extents2D destination);
}
}

View File

@ -0,0 +1,63 @@
using SharpMetal.Metal;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Metal
{
public struct RenderEncoderBindings
{
public List<Resource> Resources = new();
public List<BufferResource> VertexBuffers = new();
public List<BufferResource> FragmentBuffers = new();
public RenderEncoderBindings() { }
public readonly void Clear()
{
Resources.Clear();
VertexBuffers.Clear();
FragmentBuffers.Clear();
}
}
public struct ComputeEncoderBindings
{
public List<Resource> Resources = new();
public List<BufferResource> Buffers = new();
public ComputeEncoderBindings() { }
public readonly void Clear()
{
Resources.Clear();
Buffers.Clear();
}
}
public struct BufferResource
{
public MTLBuffer Buffer;
public ulong Offset;
public ulong Binding;
public BufferResource(MTLBuffer buffer, ulong offset, ulong binding)
{
Buffer = buffer;
Offset = offset;
Binding = binding;
}
}
public struct Resource
{
public MTLResource MtlResource;
public MTLResourceUsage ResourceUsage;
public MTLRenderStages Stages;
public Resource(MTLResource resource, MTLResourceUsage resourceUsage, MTLRenderStages stages)
{
MtlResource = resource;
ResourceUsage = resourceUsage;
Stages = stages;
}
}
}

View File

@ -0,0 +1,206 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.State;
using Ryujinx.Graphics.Shader;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[Flags]
enum DirtyFlags
{
None = 0,
RenderPipeline = 1 << 0,
ComputePipeline = 1 << 1,
DepthStencil = 1 << 2,
DepthClamp = 1 << 3,
DepthBias = 1 << 4,
CullMode = 1 << 5,
FrontFace = 1 << 6,
StencilRef = 1 << 7,
Viewports = 1 << 8,
Scissors = 1 << 9,
Uniforms = 1 << 10,
Storages = 1 << 11,
Textures = 1 << 12,
Images = 1 << 13,
ArgBuffers = Uniforms | Storages | Textures | Images,
RenderAll = RenderPipeline | DepthStencil | DepthClamp | DepthBias | CullMode | FrontFace | StencilRef | Viewports | Scissors | ArgBuffers,
ComputeAll = ComputePipeline | ArgBuffers,
All = RenderAll | ComputeAll,
}
record struct BufferRef
{
public Auto<DisposableBuffer> Buffer;
public BufferRange? Range;
public BufferRef(Auto<DisposableBuffer> buffer)
{
Buffer = buffer;
}
public BufferRef(Auto<DisposableBuffer> buffer, ref BufferRange range)
{
Buffer = buffer;
Range = range;
}
}
record struct TextureRef
{
public ShaderStage Stage;
public TextureBase Storage;
public Auto<DisposableSampler> Sampler;
public Format ImageFormat;
public TextureRef(ShaderStage stage, TextureBase storage, Auto<DisposableSampler> sampler)
{
Stage = stage;
Storage = storage;
Sampler = sampler;
}
}
record struct ImageRef
{
public ShaderStage Stage;
public Texture Storage;
public ImageRef(ShaderStage stage, Texture storage)
{
Stage = stage;
Storage = storage;
}
}
struct PredrawState
{
public MTLCullMode CullMode;
public DepthStencilUid DepthStencilUid;
public PrimitiveTopology Topology;
public MTLViewport[] Viewports;
}
struct RenderTargetCopy
{
public MTLScissorRect[] Scissors;
public Texture DepthStencil;
public Texture[] RenderTargets;
}
[SupportedOSPlatform("macos")]
class EncoderState
{
public Program RenderProgram = null;
public Program ComputeProgram = null;
public PipelineState Pipeline;
public DepthStencilUid DepthStencilUid;
public readonly record struct ArrayRef<T>(ShaderStage Stage, T Array);
public readonly BufferRef[] UniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
public readonly BufferRef[] StorageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
public readonly TextureRef[] TextureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
public readonly ImageRef[] ImageRefs = new ImageRef[Constants.MaxImageBindings * 2];
public ArrayRef<TextureArray>[] TextureArrayRefs = [];
public ArrayRef<ImageArray>[] ImageArrayRefs = [];
public ArrayRef<TextureArray>[] TextureArrayExtraRefs = [];
public ArrayRef<ImageArray>[] ImageArrayExtraRefs = [];
public IndexBufferState IndexBuffer = default;
public MTLDepthClipMode DepthClipMode = MTLDepthClipMode.Clip;
public float DepthBias;
public float SlopeScale;
public float Clamp;
public int BackRefValue = 0;
public int FrontRefValue = 0;
public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
public MTLCullMode CullMode = MTLCullMode.None;
public MTLWinding Winding = MTLWinding.CounterClockwise;
public bool CullBoth = false;
public MTLViewport[] Viewports = new MTLViewport[Constants.MaxViewports];
public MTLScissorRect[] Scissors = new MTLScissorRect[Constants.MaxViewports];
// Changes to attachments take recreation!
public Texture DepthStencil;
public Texture[] RenderTargets = new Texture[Constants.MaxColorAttachments];
public ITexture PreMaskDepthStencil = default;
public ITexture[] PreMaskRenderTargets;
public bool FramebufferUsingColorWriteMask;
public Array8<ColorBlendStateUid> StoredBlend;
public ColorF BlendColor = new();
public readonly VertexBufferState[] VertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers];
public readonly VertexAttribDescriptor[] VertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttributes];
// Dirty flags
public DirtyFlags Dirty = DirtyFlags.None;
// Only to be used for present
public bool ClearLoadAction = false;
public RenderEncoderBindings RenderEncoderBindings = new();
public ComputeEncoderBindings ComputeEncoderBindings = new();
public EncoderState()
{
Pipeline.Initialize();
DepthStencilUid.DepthCompareFunction = MTLCompareFunction.Always;
}
public RenderTargetCopy InheritForClear(EncoderState other, bool depth, int singleIndex = -1)
{
// Inherit render target related information without causing a render encoder split.
var oldState = new RenderTargetCopy
{
Scissors = other.Scissors,
RenderTargets = other.RenderTargets,
DepthStencil = other.DepthStencil
};
Scissors = other.Scissors;
RenderTargets = other.RenderTargets;
DepthStencil = other.DepthStencil;
Pipeline.ColorBlendAttachmentStateCount = other.Pipeline.ColorBlendAttachmentStateCount;
Pipeline.Internal.ColorBlendState = other.Pipeline.Internal.ColorBlendState;
Pipeline.DepthStencilFormat = other.Pipeline.DepthStencilFormat;
ref var blendStates = ref Pipeline.Internal.ColorBlendState;
// Mask out irrelevant attachments.
for (int i = 0; i < blendStates.Length; i++)
{
if (depth || (singleIndex != -1 && singleIndex != i))
{
blendStates[i].WriteMask = MTLColorWriteMask.None;
}
}
return oldState;
}
public void Restore(RenderTargetCopy copy)
{
Scissors = copy.Scissors;
RenderTargets = copy.RenderTargets;
DepthStencil = copy.DepthStencil;
Pipeline.Internal.ResetColorState();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,293 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class EnumConversion
{
public static MTLSamplerAddressMode Convert(this AddressMode mode)
{
return mode switch
{
AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, // TODO: Should be clamp.
AddressMode.Repeat => MTLSamplerAddressMode.Repeat,
AddressMode.MirrorClamp => MTLSamplerAddressMode.MirrorClampToEdge, // TODO: Should be mirror clamp.
AddressMode.MirroredRepeat => MTLSamplerAddressMode.MirrorRepeat,
AddressMode.ClampToBorder => MTLSamplerAddressMode.ClampToBorderColor,
AddressMode.ClampToEdge => MTLSamplerAddressMode.ClampToEdge,
AddressMode.MirrorClampToEdge => MTLSamplerAddressMode.MirrorClampToEdge,
AddressMode.MirrorClampToBorder => MTLSamplerAddressMode.ClampToBorderColor, // TODO: Should be mirror clamp to border.
_ => LogInvalidAndReturn(mode, nameof(AddressMode), MTLSamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
};
}
public static MTLBlendFactor Convert(this BlendFactor factor)
{
return factor switch
{
BlendFactor.Zero or BlendFactor.ZeroGl => MTLBlendFactor.Zero,
BlendFactor.One or BlendFactor.OneGl => MTLBlendFactor.One,
BlendFactor.SrcColor or BlendFactor.SrcColorGl => MTLBlendFactor.SourceColor,
BlendFactor.OneMinusSrcColor or BlendFactor.OneMinusSrcColorGl => MTLBlendFactor.OneMinusSourceColor,
BlendFactor.SrcAlpha or BlendFactor.SrcAlphaGl => MTLBlendFactor.SourceAlpha,
BlendFactor.OneMinusSrcAlpha or BlendFactor.OneMinusSrcAlphaGl => MTLBlendFactor.OneMinusSourceAlpha,
BlendFactor.DstAlpha or BlendFactor.DstAlphaGl => MTLBlendFactor.DestinationAlpha,
BlendFactor.OneMinusDstAlpha or BlendFactor.OneMinusDstAlphaGl => MTLBlendFactor.OneMinusDestinationAlpha,
BlendFactor.DstColor or BlendFactor.DstColorGl => MTLBlendFactor.DestinationColor,
BlendFactor.OneMinusDstColor or BlendFactor.OneMinusDstColorGl => MTLBlendFactor.OneMinusDestinationColor,
BlendFactor.SrcAlphaSaturate or BlendFactor.SrcAlphaSaturateGl => MTLBlendFactor.SourceAlphaSaturated,
BlendFactor.Src1Color or BlendFactor.Src1ColorGl => MTLBlendFactor.Source1Color,
BlendFactor.OneMinusSrc1Color or BlendFactor.OneMinusSrc1ColorGl => MTLBlendFactor.OneMinusSource1Color,
BlendFactor.Src1Alpha or BlendFactor.Src1AlphaGl => MTLBlendFactor.Source1Alpha,
BlendFactor.OneMinusSrc1Alpha or BlendFactor.OneMinusSrc1AlphaGl => MTLBlendFactor.OneMinusSource1Alpha,
BlendFactor.ConstantColor => MTLBlendFactor.BlendColor,
BlendFactor.OneMinusConstantColor => MTLBlendFactor.OneMinusBlendColor,
BlendFactor.ConstantAlpha => MTLBlendFactor.BlendAlpha,
BlendFactor.OneMinusConstantAlpha => MTLBlendFactor.OneMinusBlendAlpha,
_ => LogInvalidAndReturn(factor, nameof(BlendFactor), MTLBlendFactor.Zero)
};
}
public static MTLBlendOperation Convert(this BlendOp op)
{
return op switch
{
BlendOp.Add or BlendOp.AddGl => MTLBlendOperation.Add,
BlendOp.Subtract or BlendOp.SubtractGl => MTLBlendOperation.Subtract,
BlendOp.ReverseSubtract or BlendOp.ReverseSubtractGl => MTLBlendOperation.ReverseSubtract,
BlendOp.Minimum => MTLBlendOperation.Min,
BlendOp.Maximum => MTLBlendOperation.Max,
_ => LogInvalidAndReturn(op, nameof(BlendOp), MTLBlendOperation.Add)
};
}
public static MTLCompareFunction Convert(this CompareOp op)
{
return op switch
{
CompareOp.Never or CompareOp.NeverGl => MTLCompareFunction.Never,
CompareOp.Less or CompareOp.LessGl => MTLCompareFunction.Less,
CompareOp.Equal or CompareOp.EqualGl => MTLCompareFunction.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => MTLCompareFunction.LessEqual,
CompareOp.Greater or CompareOp.GreaterGl => MTLCompareFunction.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => MTLCompareFunction.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => MTLCompareFunction.GreaterEqual,
CompareOp.Always or CompareOp.AlwaysGl => MTLCompareFunction.Always,
_ => LogInvalidAndReturn(op, nameof(CompareOp), MTLCompareFunction.Never)
};
}
public static MTLCullMode Convert(this Face face)
{
return face switch
{
Face.Back => MTLCullMode.Back,
Face.Front => MTLCullMode.Front,
Face.FrontAndBack => MTLCullMode.None,
_ => LogInvalidAndReturn(face, nameof(Face), MTLCullMode.Back)
};
}
public static MTLWinding Convert(this FrontFace frontFace)
{
// The viewport is flipped vertically, therefore we need to switch the winding order as well
return frontFace switch
{
FrontFace.Clockwise => MTLWinding.CounterClockwise,
FrontFace.CounterClockwise => MTLWinding.Clockwise,
_ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise)
};
}
public static MTLIndexType Convert(this IndexType type)
{
return type switch
{
IndexType.UShort => MTLIndexType.UInt16,
IndexType.UInt => MTLIndexType.UInt32,
_ => LogInvalidAndReturn(type, nameof(IndexType), MTLIndexType.UInt16)
};
}
public static MTLLogicOperation Convert(this LogicalOp op)
{
return op switch
{
LogicalOp.Clear => MTLLogicOperation.Clear,
LogicalOp.And => MTLLogicOperation.And,
LogicalOp.AndReverse => MTLLogicOperation.AndReverse,
LogicalOp.Copy => MTLLogicOperation.Copy,
LogicalOp.AndInverted => MTLLogicOperation.AndInverted,
LogicalOp.Noop => MTLLogicOperation.Noop,
LogicalOp.Xor => MTLLogicOperation.Xor,
LogicalOp.Or => MTLLogicOperation.Or,
LogicalOp.Nor => MTLLogicOperation.Nor,
LogicalOp.Equiv => MTLLogicOperation.Equivalence,
LogicalOp.Invert => MTLLogicOperation.Invert,
LogicalOp.OrReverse => MTLLogicOperation.OrReverse,
LogicalOp.CopyInverted => MTLLogicOperation.CopyInverted,
LogicalOp.OrInverted => MTLLogicOperation.OrInverted,
LogicalOp.Nand => MTLLogicOperation.Nand,
LogicalOp.Set => MTLLogicOperation.Set,
_ => LogInvalidAndReturn(op, nameof(LogicalOp), MTLLogicOperation.And)
};
}
public static MTLSamplerMinMagFilter Convert(this MagFilter filter)
{
return filter switch
{
MagFilter.Nearest => MTLSamplerMinMagFilter.Nearest,
MagFilter.Linear => MTLSamplerMinMagFilter.Linear,
_ => LogInvalidAndReturn(filter, nameof(MagFilter), MTLSamplerMinMagFilter.Nearest)
};
}
public static (MTLSamplerMinMagFilter, MTLSamplerMipFilter) Convert(this MinFilter filter)
{
return filter switch
{
MinFilter.Nearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
MinFilter.NearestMipmapNearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.LinearMipmapNearest => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Nearest),
MinFilter.NearestMipmapLinear => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Linear),
MinFilter.LinearMipmapLinear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
_ => LogInvalidAndReturn(filter, nameof(MinFilter), (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest))
};
}
public static MTLPrimitiveType Convert(this PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Points => MTLPrimitiveType.Point,
PrimitiveTopology.Lines => MTLPrimitiveType.Line,
PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip,
PrimitiveTopology.Triangles => MTLPrimitiveType.Triangle,
PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip,
_ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), MTLPrimitiveType.Triangle)
};
}
public static MTLStencilOperation Convert(this StencilOp op)
{
return op switch
{
StencilOp.Keep or StencilOp.KeepGl => MTLStencilOperation.Keep,
StencilOp.Zero or StencilOp.ZeroGl => MTLStencilOperation.Zero,
StencilOp.Replace or StencilOp.ReplaceGl => MTLStencilOperation.Replace,
StencilOp.IncrementAndClamp or StencilOp.IncrementAndClampGl => MTLStencilOperation.IncrementClamp,
StencilOp.DecrementAndClamp or StencilOp.DecrementAndClampGl => MTLStencilOperation.DecrementClamp,
StencilOp.Invert or StencilOp.InvertGl => MTLStencilOperation.Invert,
StencilOp.IncrementAndWrap or StencilOp.IncrementAndWrapGl => MTLStencilOperation.IncrementWrap,
StencilOp.DecrementAndWrap or StencilOp.DecrementAndWrapGl => MTLStencilOperation.DecrementWrap,
_ => LogInvalidAndReturn(op, nameof(StencilOp), MTLStencilOperation.Keep)
};
}
public static MTLTextureType Convert(this Target target)
{
return target switch
{
Target.TextureBuffer => MTLTextureType.TextureBuffer,
Target.Texture1D => MTLTextureType.Type1D,
Target.Texture1DArray => MTLTextureType.Type1DArray,
Target.Texture2D => MTLTextureType.Type2D,
Target.Texture2DArray => MTLTextureType.Type2DArray,
Target.Texture2DMultisample => MTLTextureType.Type2DMultisample,
Target.Texture2DMultisampleArray => MTLTextureType.Type2DMultisampleArray,
Target.Texture3D => MTLTextureType.Type3D,
Target.Cubemap => MTLTextureType.Cube,
Target.CubemapArray => MTLTextureType.CubeArray,
_ => LogInvalidAndReturn(target, nameof(Target), MTLTextureType.Type2D)
};
}
public static MTLTextureSwizzle Convert(this SwizzleComponent swizzleComponent)
{
return swizzleComponent switch
{
SwizzleComponent.Zero => MTLTextureSwizzle.Zero,
SwizzleComponent.One => MTLTextureSwizzle.One,
SwizzleComponent.Red => MTLTextureSwizzle.Red,
SwizzleComponent.Green => MTLTextureSwizzle.Green,
SwizzleComponent.Blue => MTLTextureSwizzle.Blue,
SwizzleComponent.Alpha => MTLTextureSwizzle.Alpha,
_ => LogInvalidAndReturn(swizzleComponent, nameof(SwizzleComponent), MTLTextureSwizzle.Zero)
};
}
public static MTLVertexFormat Convert(this Format format)
{
return format switch
{
Format.R16Float => MTLVertexFormat.Half,
Format.R16G16Float => MTLVertexFormat.Half2,
Format.R16G16B16Float => MTLVertexFormat.Half3,
Format.R16G16B16A16Float => MTLVertexFormat.Half4,
Format.R32Float => MTLVertexFormat.Float,
Format.R32G32Float => MTLVertexFormat.Float2,
Format.R32G32B32Float => MTLVertexFormat.Float3,
Format.R11G11B10Float => MTLVertexFormat.FloatRG11B10,
Format.R32G32B32A32Float => MTLVertexFormat.Float4,
Format.R8Uint => MTLVertexFormat.UChar,
Format.R8G8Uint => MTLVertexFormat.UChar2,
Format.R8G8B8Uint => MTLVertexFormat.UChar3,
Format.R8G8B8A8Uint => MTLVertexFormat.UChar4,
Format.R16Uint => MTLVertexFormat.UShort,
Format.R16G16Uint => MTLVertexFormat.UShort2,
Format.R16G16B16Uint => MTLVertexFormat.UShort3,
Format.R16G16B16A16Uint => MTLVertexFormat.UShort4,
Format.R32Uint => MTLVertexFormat.UInt,
Format.R32G32Uint => MTLVertexFormat.UInt2,
Format.R32G32B32Uint => MTLVertexFormat.UInt3,
Format.R32G32B32A32Uint => MTLVertexFormat.UInt4,
Format.R8Sint => MTLVertexFormat.Char,
Format.R8G8Sint => MTLVertexFormat.Char2,
Format.R8G8B8Sint => MTLVertexFormat.Char3,
Format.R8G8B8A8Sint => MTLVertexFormat.Char4,
Format.R16Sint => MTLVertexFormat.Short,
Format.R16G16Sint => MTLVertexFormat.Short2,
Format.R16G16B16Sint => MTLVertexFormat.Short3,
Format.R16G16B16A16Sint => MTLVertexFormat.Short4,
Format.R32Sint => MTLVertexFormat.Int,
Format.R32G32Sint => MTLVertexFormat.Int2,
Format.R32G32B32Sint => MTLVertexFormat.Int3,
Format.R32G32B32A32Sint => MTLVertexFormat.Int4,
Format.R8Unorm => MTLVertexFormat.UCharNormalized,
Format.R8G8Unorm => MTLVertexFormat.UChar2Normalized,
Format.R8G8B8Unorm => MTLVertexFormat.UChar3Normalized,
Format.R8G8B8A8Unorm => MTLVertexFormat.UChar4Normalized,
Format.R16Unorm => MTLVertexFormat.UShortNormalized,
Format.R16G16Unorm => MTLVertexFormat.UShort2Normalized,
Format.R16G16B16Unorm => MTLVertexFormat.UShort3Normalized,
Format.R16G16B16A16Unorm => MTLVertexFormat.UShort4Normalized,
Format.R10G10B10A2Unorm => MTLVertexFormat.UInt1010102Normalized,
Format.R8Snorm => MTLVertexFormat.CharNormalized,
Format.R8G8Snorm => MTLVertexFormat.Char2Normalized,
Format.R8G8B8Snorm => MTLVertexFormat.Char3Normalized,
Format.R8G8B8A8Snorm => MTLVertexFormat.Char4Normalized,
Format.R16Snorm => MTLVertexFormat.ShortNormalized,
Format.R16G16Snorm => MTLVertexFormat.Short2Normalized,
Format.R16G16B16Snorm => MTLVertexFormat.Short3Normalized,
Format.R16G16B16A16Snorm => MTLVertexFormat.Short4Normalized,
Format.R10G10B10A2Snorm => MTLVertexFormat.Int1010102Normalized,
_ => LogInvalidAndReturn(format, nameof(Format), MTLVertexFormat.Float4)
};
}
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
{
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
return defaultValue;
}
}
}

View File

@ -0,0 +1,77 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class FenceHolder : IDisposable
{
private MTLCommandBuffer _fence;
private int _referenceCount;
private bool _disposed;
public FenceHolder(MTLCommandBuffer fence)
{
_fence = fence;
_referenceCount = 1;
}
public MTLCommandBuffer GetUnsafe()
{
return _fence;
}
public bool TryGet(out MTLCommandBuffer fence)
{
int lastValue;
do
{
lastValue = _referenceCount;
if (lastValue == 0)
{
fence = default;
return false;
}
} while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
fence = _fence;
return true;
}
public MTLCommandBuffer Get()
{
Interlocked.Increment(ref _referenceCount);
return _fence;
}
public void Put()
{
if (Interlocked.Decrement(ref _referenceCount) == 0)
{
_fence = default;
}
}
public void Wait()
{
_fence.WaitUntilCompleted();
}
public bool IsSignaled()
{
return _fence.Status == MTLCommandBufferStatus.Completed;
}
public void Dispose()
{
if (!_disposed)
{
Put();
_disposed = true;
}
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Metal
{
class FormatConverter
{
public static void ConvertD24S8ToD32FS8(Span<byte> output, ReadOnlySpan<byte> input)
{
const float UnormToFloat = 1f / 0xffffff;
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
int i = 0;
for (; i < inputUint.Length; i++)
{
uint depthStencil = inputUint[i];
uint depth = depthStencil >> 8;
uint stencil = depthStencil & 0xff;
int j = i * 2;
outputUint[j] = (uint)BitConverter.SingleToInt32Bits(depth * UnormToFloat);
outputUint[j + 1] = stencil;
}
}
public static void ConvertD32FS8ToD24S8(Span<byte> output, ReadOnlySpan<byte> input)
{
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
int i = 0;
for (; i < inputUint.Length; i += 2)
{
float depth = BitConverter.Int32BitsToSingle((int)inputUint[i]);
uint stencil = inputUint[i + 1];
uint depthStencil = (Math.Clamp((uint)(depth * 0xffffff), 0, 0xffffff) << 8) | (stencil & 0xff);
int j = i >> 1;
outputUint[j] = depthStencil;
}
}
}
}

View File

@ -0,0 +1,196 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class FormatTable
{
private static readonly MTLPixelFormat[] _table;
static FormatTable()
{
_table = new MTLPixelFormat[Enum.GetNames(typeof(Format)).Length];
Add(Format.R8Unorm, MTLPixelFormat.R8Unorm);
Add(Format.R8Snorm, MTLPixelFormat.R8Snorm);
Add(Format.R8Uint, MTLPixelFormat.R8Uint);
Add(Format.R8Sint, MTLPixelFormat.R8Sint);
Add(Format.R16Float, MTLPixelFormat.R16Float);
Add(Format.R16Unorm, MTLPixelFormat.R16Unorm);
Add(Format.R16Snorm, MTLPixelFormat.R16Snorm);
Add(Format.R16Uint, MTLPixelFormat.R16Uint);
Add(Format.R16Sint, MTLPixelFormat.R16Sint);
Add(Format.R32Float, MTLPixelFormat.R32Float);
Add(Format.R32Uint, MTLPixelFormat.R32Uint);
Add(Format.R32Sint, MTLPixelFormat.R32Sint);
Add(Format.R8G8Unorm, MTLPixelFormat.RG8Unorm);
Add(Format.R8G8Snorm, MTLPixelFormat.RG8Snorm);
Add(Format.R8G8Uint, MTLPixelFormat.RG8Uint);
Add(Format.R8G8Sint, MTLPixelFormat.RG8Sint);
Add(Format.R16G16Float, MTLPixelFormat.RG16Float);
Add(Format.R16G16Unorm, MTLPixelFormat.RG16Unorm);
Add(Format.R16G16Snorm, MTLPixelFormat.RG16Snorm);
Add(Format.R16G16Uint, MTLPixelFormat.RG16Uint);
Add(Format.R16G16Sint, MTLPixelFormat.RG16Sint);
Add(Format.R32G32Float, MTLPixelFormat.RG32Float);
Add(Format.R32G32Uint, MTLPixelFormat.RG32Uint);
Add(Format.R32G32Sint, MTLPixelFormat.RG32Sint);
// Add(Format.R8G8B8Unorm, MTLPixelFormat.R8G8B8Unorm);
// Add(Format.R8G8B8Snorm, MTLPixelFormat.R8G8B8Snorm);
// Add(Format.R8G8B8Uint, MTLPixelFormat.R8G8B8Uint);
// Add(Format.R8G8B8Sint, MTLPixelFormat.R8G8B8Sint);
// Add(Format.R16G16B16Float, MTLPixelFormat.R16G16B16Float);
// Add(Format.R16G16B16Unorm, MTLPixelFormat.R16G16B16Unorm);
// Add(Format.R16G16B16Snorm, MTLPixelFormat.R16G16B16SNorm);
// Add(Format.R16G16B16Uint, MTLPixelFormat.R16G16B16Uint);
// Add(Format.R16G16B16Sint, MTLPixelFormat.R16G16B16Sint);
// Add(Format.R32G32B32Float, MTLPixelFormat.R32G32B32Sfloat);
// Add(Format.R32G32B32Uint, MTLPixelFormat.R32G32B32Uint);
// Add(Format.R32G32B32Sint, MTLPixelFormat.R32G32B32Sint);
Add(Format.R8G8B8A8Unorm, MTLPixelFormat.RGBA8Unorm);
Add(Format.R8G8B8A8Snorm, MTLPixelFormat.RGBA8Snorm);
Add(Format.R8G8B8A8Uint, MTLPixelFormat.RGBA8Uint);
Add(Format.R8G8B8A8Sint, MTLPixelFormat.RGBA8Sint);
Add(Format.R16G16B16A16Float, MTLPixelFormat.RGBA16Float);
Add(Format.R16G16B16A16Unorm, MTLPixelFormat.RGBA16Unorm);
Add(Format.R16G16B16A16Snorm, MTLPixelFormat.RGBA16Snorm);
Add(Format.R16G16B16A16Uint, MTLPixelFormat.RGBA16Uint);
Add(Format.R16G16B16A16Sint, MTLPixelFormat.RGBA16Sint);
Add(Format.R32G32B32A32Float, MTLPixelFormat.RGBA32Float);
Add(Format.R32G32B32A32Uint, MTLPixelFormat.RGBA32Uint);
Add(Format.R32G32B32A32Sint, MTLPixelFormat.RGBA32Sint);
Add(Format.S8Uint, MTLPixelFormat.Stencil8);
Add(Format.D16Unorm, MTLPixelFormat.Depth16Unorm);
Add(Format.S8UintD24Unorm, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.X8UintD24Unorm, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.D32Float, MTLPixelFormat.Depth32Float);
Add(Format.D24UnormS8Uint, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.D32FloatS8Uint, MTLPixelFormat.Depth32FloatStencil8);
Add(Format.R8G8B8A8Srgb, MTLPixelFormat.RGBA8UnormsRGB);
// Add(Format.R4G4Unorm, MTLPixelFormat.R4G4Unorm);
Add(Format.R4G4B4A4Unorm, MTLPixelFormat.RGBA8Unorm);
// Add(Format.R5G5B5X1Unorm, MTLPixelFormat.R5G5B5X1Unorm);
Add(Format.R5G5B5A1Unorm, MTLPixelFormat.BGR5A1Unorm);
Add(Format.R5G6B5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.R10G10B10A2Unorm, MTLPixelFormat.RGB10A2Unorm);
Add(Format.R10G10B10A2Uint, MTLPixelFormat.RGB10A2Uint);
Add(Format.R11G11B10Float, MTLPixelFormat.RG11B10Float);
Add(Format.R9G9B9E5Float, MTLPixelFormat.RGB9E5Float);
Add(Format.Bc1RgbaUnorm, MTLPixelFormat.BC1RGBA);
Add(Format.Bc2Unorm, MTLPixelFormat.BC2RGBA);
Add(Format.Bc3Unorm, MTLPixelFormat.BC3RGBA);
Add(Format.Bc1RgbaSrgb, MTLPixelFormat.BC1RGBAsRGB);
Add(Format.Bc2Srgb, MTLPixelFormat.BC2RGBAsRGB);
Add(Format.Bc3Srgb, MTLPixelFormat.BC3RGBAsRGB);
Add(Format.Bc4Unorm, MTLPixelFormat.BC4RUnorm);
Add(Format.Bc4Snorm, MTLPixelFormat.BC4RSnorm);
Add(Format.Bc5Unorm, MTLPixelFormat.BC5RGUnorm);
Add(Format.Bc5Snorm, MTLPixelFormat.BC5RGSnorm);
Add(Format.Bc7Unorm, MTLPixelFormat.BC7RGBAUnorm);
Add(Format.Bc7Srgb, MTLPixelFormat.BC7RGBAUnormsRGB);
Add(Format.Bc6HSfloat, MTLPixelFormat.BC6HRGBFloat);
Add(Format.Bc6HUfloat, MTLPixelFormat.BC6HRGBUfloat);
Add(Format.Etc2RgbUnorm, MTLPixelFormat.ETC2RGB8);
// Add(Format.Etc2RgbaUnorm, MTLPixelFormat.ETC2RGBA8);
Add(Format.Etc2RgbPtaUnorm, MTLPixelFormat.ETC2RGB8A1);
Add(Format.Etc2RgbSrgb, MTLPixelFormat.ETC2RGB8sRGB);
// Add(Format.Etc2RgbaSrgb, MTLPixelFormat.ETC2RGBA8sRGB);
Add(Format.Etc2RgbPtaSrgb, MTLPixelFormat.ETC2RGB8A1sRGB);
// Add(Format.R8Uscaled, MTLPixelFormat.R8Uscaled);
// Add(Format.R8Sscaled, MTLPixelFormat.R8Sscaled);
// Add(Format.R16Uscaled, MTLPixelFormat.R16Uscaled);
// Add(Format.R16Sscaled, MTLPixelFormat.R16Sscaled);
// Add(Format.R32Uscaled, MTLPixelFormat.R32Uscaled);
// Add(Format.R32Sscaled, MTLPixelFormat.R32Sscaled);
// Add(Format.R8G8Uscaled, MTLPixelFormat.R8G8Uscaled);
// Add(Format.R8G8Sscaled, MTLPixelFormat.R8G8Sscaled);
// Add(Format.R16G16Uscaled, MTLPixelFormat.R16G16Uscaled);
// Add(Format.R16G16Sscaled, MTLPixelFormat.R16G16Sscaled);
// Add(Format.R32G32Uscaled, MTLPixelFormat.R32G32Uscaled);
// Add(Format.R32G32Sscaled, MTLPixelFormat.R32G32Sscaled);
// Add(Format.R8G8B8Uscaled, MTLPixelFormat.R8G8B8Uscaled);
// Add(Format.R8G8B8Sscaled, MTLPixelFormat.R8G8B8Sscaled);
// Add(Format.R16G16B16Uscaled, MTLPixelFormat.R16G16B16Uscaled);
// Add(Format.R16G16B16Sscaled, MTLPixelFormat.R16G16B16Sscaled);
// Add(Format.R32G32B32Uscaled, MTLPixelFormat.R32G32B32Uscaled);
// Add(Format.R32G32B32Sscaled, MTLPixelFormat.R32G32B32Sscaled);
// Add(Format.R8G8B8A8Uscaled, MTLPixelFormat.R8G8B8A8Uscaled);
// Add(Format.R8G8B8A8Sscaled, MTLPixelFormat.R8G8B8A8Sscaled);
// Add(Format.R16G16B16A16Uscaled, MTLPixelFormat.R16G16B16A16Uscaled);
// Add(Format.R16G16B16A16Sscaled, MTLPixelFormat.R16G16B16A16Sscaled);
// Add(Format.R32G32B32A32Uscaled, MTLPixelFormat.R32G32B32A32Uscaled);
// Add(Format.R32G32B32A32Sscaled, MTLPixelFormat.R32G32B32A32Sscaled);
// Add(Format.R10G10B10A2Snorm, MTLPixelFormat.A2B10G10R10SNormPack32);
// Add(Format.R10G10B10A2Sint, MTLPixelFormat.A2B10G10R10SintPack32);
// Add(Format.R10G10B10A2Uscaled, MTLPixelFormat.A2B10G10R10UscaledPack32);
// Add(Format.R10G10B10A2Sscaled, MTLPixelFormat.A2B10G10R10SscaledPack32);
Add(Format.Astc4x4Unorm, MTLPixelFormat.ASTC4x4LDR);
Add(Format.Astc5x4Unorm, MTLPixelFormat.ASTC5x4LDR);
Add(Format.Astc5x5Unorm, MTLPixelFormat.ASTC5x5LDR);
Add(Format.Astc6x5Unorm, MTLPixelFormat.ASTC6x5LDR);
Add(Format.Astc6x6Unorm, MTLPixelFormat.ASTC6x6LDR);
Add(Format.Astc8x5Unorm, MTLPixelFormat.ASTC8x5LDR);
Add(Format.Astc8x6Unorm, MTLPixelFormat.ASTC8x6LDR);
Add(Format.Astc8x8Unorm, MTLPixelFormat.ASTC8x8LDR);
Add(Format.Astc10x5Unorm, MTLPixelFormat.ASTC10x5LDR);
Add(Format.Astc10x6Unorm, MTLPixelFormat.ASTC10x6LDR);
Add(Format.Astc10x8Unorm, MTLPixelFormat.ASTC10x8LDR);
Add(Format.Astc10x10Unorm, MTLPixelFormat.ASTC10x10LDR);
Add(Format.Astc12x10Unorm, MTLPixelFormat.ASTC12x10LDR);
Add(Format.Astc12x12Unorm, MTLPixelFormat.ASTC12x12LDR);
Add(Format.Astc4x4Srgb, MTLPixelFormat.ASTC4x4sRGB);
Add(Format.Astc5x4Srgb, MTLPixelFormat.ASTC5x4sRGB);
Add(Format.Astc5x5Srgb, MTLPixelFormat.ASTC5x5sRGB);
Add(Format.Astc6x5Srgb, MTLPixelFormat.ASTC6x5sRGB);
Add(Format.Astc6x6Srgb, MTLPixelFormat.ASTC6x6sRGB);
Add(Format.Astc8x5Srgb, MTLPixelFormat.ASTC8x5sRGB);
Add(Format.Astc8x6Srgb, MTLPixelFormat.ASTC8x6sRGB);
Add(Format.Astc8x8Srgb, MTLPixelFormat.ASTC8x8sRGB);
Add(Format.Astc10x5Srgb, MTLPixelFormat.ASTC10x5sRGB);
Add(Format.Astc10x6Srgb, MTLPixelFormat.ASTC10x6sRGB);
Add(Format.Astc10x8Srgb, MTLPixelFormat.ASTC10x8sRGB);
Add(Format.Astc10x10Srgb, MTLPixelFormat.ASTC10x10sRGB);
Add(Format.Astc12x10Srgb, MTLPixelFormat.ASTC12x10sRGB);
Add(Format.Astc12x12Srgb, MTLPixelFormat.ASTC12x12sRGB);
Add(Format.B5G6R5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.B5G5R5A1Unorm, MTLPixelFormat.BGR5A1Unorm);
Add(Format.A1B5G5R5Unorm, MTLPixelFormat.A1BGR5Unorm);
Add(Format.B8G8R8A8Unorm, MTLPixelFormat.BGRA8Unorm);
Add(Format.B8G8R8A8Srgb, MTLPixelFormat.BGRA8UnormsRGB);
}
private static void Add(Format format, MTLPixelFormat mtlFormat)
{
_table[(int)format] = mtlFormat;
}
public static MTLPixelFormat GetFormat(Format format)
{
var mtlFormat = _table[(int)format];
if (IsD24S8(format))
{
if (!MTLDevice.CreateSystemDefaultDevice().Depth24Stencil8PixelFormatSupported)
{
mtlFormat = MTLPixelFormat.Depth32FloatStencil8;
}
}
if (mtlFormat == MTLPixelFormat.Invalid)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Format {format} is not supported by the host.");
}
return mtlFormat;
}
public static bool IsD24S8(Format format)
{
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm;
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Metal
{
static partial class HardwareInfoTools
{
private readonly static IntPtr _kCFAllocatorDefault = IntPtr.Zero;
private readonly static UInt32 _kCFStringEncodingASCII = 0x0600;
private const string IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit";
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
[LibraryImport(IOKit, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr IOServiceMatching(string name);
[LibraryImport(IOKit)]
private static partial IntPtr IOServiceGetMatchingService(IntPtr mainPort, IntPtr matching);
[LibraryImport(IOKit)]
private static partial IntPtr IORegistryEntryCreateCFProperty(IntPtr entry, IntPtr key, IntPtr allocator, UInt32 options);
[LibraryImport(CoreFoundation, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr CFStringCreateWithCString(IntPtr allocator, string cString, UInt32 encoding);
[LibraryImport(CoreFoundation)]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool CFStringGetCString(IntPtr theString, IntPtr buffer, long bufferSizes, UInt32 encoding);
[LibraryImport(CoreFoundation)]
public static partial IntPtr CFDataGetBytePtr(IntPtr theData);
static string GetNameFromId(uint id)
{
return id switch
{
0x1002 => "AMD",
0x106B => "Apple",
0x10DE => "NVIDIA",
0x13B5 => "ARM",
0x8086 => "Intel",
_ => $"0x{id:X}"
};
}
public static string GetVendor()
{
var serviceDict = IOServiceMatching("IOGPU");
var service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
var cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "vendor-id", _kCFStringEncodingASCII);
var cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
byte[] buffer = new byte[4];
var bufferPtr = CFDataGetBytePtr(cfProperty);
Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
var vendorId = BitConverter.ToUInt32(buffer);
return GetNameFromId(vendorId);
}
public static string GetModel()
{
var serviceDict = IOServiceMatching("IOGPU");
var service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
var cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "model", _kCFStringEncodingASCII);
var cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
char[] buffer = new char[64];
IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
if (CFStringGetCString(cfProperty, bufferPtr, buffer.Length, _kCFStringEncodingASCII))
{
var model = Marshal.PtrToStringUTF8(bufferPtr);
Marshal.FreeHGlobal(bufferPtr);
return model;
}
return "";
}
}
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Metal
{
interface IRefEquatable<T>
{
bool Equals(ref T other);
}
class HashTableSlim<TKey, TValue> where TKey : IRefEquatable<TKey>
{
private const int TotalBuckets = 16; // Must be power of 2
private const int TotalBucketsMask = TotalBuckets - 1;
private struct Entry
{
public int Hash;
public TKey Key;
public TValue Value;
}
private struct Bucket
{
public int Length;
public Entry[] Entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<Entry> AsSpan()
{
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
}
}
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
public IEnumerable<TKey> Keys
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Key;
}
}
}
}
public IEnumerable<TValue> Values
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Value;
}
}
}
}
public void Add(ref TKey key, TValue value)
{
var entry = new Entry
{
Hash = key.GetHashCode(),
Key = key,
Value = value,
};
int hashCode = key.GetHashCode();
int bucketIndex = hashCode & TotalBucketsMask;
ref var bucket = ref _hashTable[bucketIndex];
if (bucket.Entries != null)
{
int index = bucket.Length;
if (index >= bucket.Entries.Length)
{
Array.Resize(ref bucket.Entries, index + 1);
}
bucket.Entries[index] = entry;
}
else
{
bucket.Entries = new[]
{
entry,
};
}
bucket.Length++;
}
public bool Remove(ref TKey key)
{
int hashCode = key.GetHashCode();
ref var bucket = ref _hashTable[hashCode & TotalBucketsMask];
var entries = bucket.AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
entries[(i + 1)..].CopyTo(entries[i..]);
bucket.Length--;
return true;
}
}
return false;
}
public bool TryGetValue(ref TKey key, out TValue value)
{
int hashCode = key.GetHashCode();
var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
}
}

View File

@ -0,0 +1,868 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class HelperShader : IDisposable
{
private const int ConvertElementsPerWorkgroup = 32 * 100; // Work group size of 32 times 100 elements.
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
private readonly MetalRenderer _renderer;
private readonly Pipeline _pipeline;
private MTLDevice _device;
private readonly ISampler _samplerLinear;
private readonly ISampler _samplerNearest;
private readonly IProgram _programColorBlitF;
private readonly IProgram _programColorBlitI;
private readonly IProgram _programColorBlitU;
private readonly IProgram _programColorBlitMsF;
private readonly IProgram _programColorBlitMsI;
private readonly IProgram _programColorBlitMsU;
private readonly List<IProgram> _programsColorClearF = new();
private readonly List<IProgram> _programsColorClearI = new();
private readonly List<IProgram> _programsColorClearU = new();
private readonly IProgram _programDepthStencilClear;
private readonly IProgram _programStrideChange;
private readonly IProgram _programConvertD32S8ToD24S8;
private readonly IProgram _programConvertIndexBuffer;
private readonly IProgram _programDepthBlit;
private readonly IProgram _programDepthBlitMs;
private readonly IProgram _programStencilBlit;
private readonly IProgram _programStencilBlitMs;
private readonly EncoderState _helperShaderState = new();
public HelperShader(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
{
_device = device;
_renderer = renderer;
_pipeline = pipeline;
_samplerNearest = new SamplerHolder(renderer, _device, SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
_samplerLinear = new SamplerHolder(renderer, _device, SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
var blitResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build();
var blitSource = ReadMsl("Blit.metal");
var blitSourceF = blitSource.Replace("FORMAT", "float", StringComparison.Ordinal);
_programColorBlitF = new Program(renderer, device, [
new ShaderSource(blitSourceF, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var blitSourceI = blitSource.Replace("FORMAT", "int");
_programColorBlitI = new Program(renderer, device, [
new ShaderSource(blitSourceI, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceI, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var blitSourceU = blitSource.Replace("FORMAT", "uint");
_programColorBlitU = new Program(renderer, device, [
new ShaderSource(blitSourceU, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceU, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var blitMsSource = ReadMsl("BlitMs.metal");
var blitMsSourceF = blitMsSource.Replace("FORMAT", "float");
_programColorBlitMsF = new Program(renderer, device, [
new ShaderSource(blitMsSourceF, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var blitMsSourceI = blitMsSource.Replace("FORMAT", "int");
_programColorBlitMsI = new Program(renderer, device, [
new ShaderSource(blitMsSourceI, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceI, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var blitMsSourceU = blitMsSource.Replace("FORMAT", "uint");
_programColorBlitMsU = new Program(renderer, device, [
new ShaderSource(blitMsSourceU, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitMsSourceU, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var colorClearResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 0).Build();
var colorClearSource = ReadMsl("ColorClear.metal");
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "float");
_programsColorClearF.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "int");
_programsColorClearI.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var crntSource = colorClearSource.Replace("COLOR_ATTACHMENT_INDEX", i.ToString()).Replace("FORMAT", "uint");
_programsColorClearU.Add(new Program(renderer, device, [
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout));
}
var depthStencilClearSource = ReadMsl("DepthStencilClear.metal");
_programDepthStencilClear = new Program(renderer, device, [
new ShaderSource(depthStencilClearSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(depthStencilClearSource, ShaderStage.Vertex, TargetLanguage.Msl)
], colorClearResourceLayout);
var strideChangeResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
var strideChangeSource = ReadMsl("ChangeBufferStride.metal");
_programStrideChange = new Program(renderer, device, [
new ShaderSource(strideChangeSource, ShaderStage.Compute, TargetLanguage.Msl)
], strideChangeResourceLayout, new ComputeSize(64, 1, 1));
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
var convertD32S8ToD24S8Source = ReadMsl("ConvertD32S8ToD24S8.metal");
_programConvertD32S8ToD24S8 = new Program(renderer, device, [
new ShaderSource(convertD32S8ToD24S8Source, ShaderStage.Compute, TargetLanguage.Msl)
], convertD32S8ToD24S8ResourceLayout, new ComputeSize(64, 1, 1));
var convertIndexBufferLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
var convertIndexBufferSource = ReadMsl("ConvertIndexBuffer.metal");
_programConvertIndexBuffer = new Program(renderer, device, [
new ShaderSource(convertIndexBufferSource, ShaderStage.Compute, TargetLanguage.Msl)
], convertIndexBufferLayout, new ComputeSize(16, 1, 1));
var depthBlitSource = ReadMsl("DepthBlit.metal");
_programDepthBlit = new Program(renderer, device, [
new ShaderSource(depthBlitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var depthBlitMsSource = ReadMsl("DepthBlitMs.metal");
_programDepthBlitMs = new Program(renderer, device, [
new ShaderSource(depthBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var stencilBlitSource = ReadMsl("StencilBlit.metal");
_programStencilBlit = new Program(renderer, device, [
new ShaderSource(stencilBlitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
var stencilBlitMsSource = ReadMsl("StencilBlitMs.metal");
_programStencilBlitMs = new Program(renderer, device, [
new ShaderSource(stencilBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSourceF, ShaderStage.Vertex, TargetLanguage.Msl)
], blitResourceLayout);
}
private static string ReadMsl(string fileName)
{
var msl = EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
#pragma warning disable IDE0055 // Disable formatting
msl = msl.Replace("CONSTANT_BUFFERS_INDEX", $"{Constants.ConstantBuffersIndex}")
.Replace("STORAGE_BUFFERS_INDEX", $"{Constants.StorageBuffersIndex}")
.Replace("TEXTURES_INDEX", $"{Constants.TexturesIndex}")
.Replace("IMAGES_INDEX", $"{Constants.ImagesIndex}");
#pragma warning restore IDE0055
return msl;
}
public unsafe void BlitColor(
CommandBufferScoped cbs,
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
bool clear = false)
{
_pipeline.SwapState(_helperShaderState);
const int RegionBufferSize = 16;
var sampler = linearFilter ? _samplerLinear : _samplerNearest;
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / (float)src.Width;
region[1] = srcRegion.X2 / (float)src.Width;
region[2] = srcRegion.Y1 / (float)src.Height;
region[3] = srcRegion.Y2 / (float)src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
using var buffer = _renderer.BufferManager.ReserveOrCreate(cbs, RegionBufferSize);
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
var rect = new Rectangle<float>(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
Span<Viewport> viewports = stackalloc Viewport[16];
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
if (dstIsDepthOrStencil)
{
// TODO: Depth & stencil blit!
Logger.Warning?.PrintMsg(LogClass.Gpu, "Requested a depth or stencil blit!");
_pipeline.SwapState(null);
return;
}
var debugGroupName = "Blit Color ";
if (src.Info.Target.IsMultisample())
{
if (dst.Info.Format.IsSint())
{
debugGroupName += "MS Int";
_pipeline.SetProgram(_programColorBlitMsI);
}
else if (dst.Info.Format.IsUint())
{
debugGroupName += "MS UInt";
_pipeline.SetProgram(_programColorBlitMsU);
}
else
{
debugGroupName += "MS Float";
_pipeline.SetProgram(_programColorBlitMsF);
}
}
else
{
if (dst.Info.Format.IsSint())
{
debugGroupName += "Int";
_pipeline.SetProgram(_programColorBlitI);
}
else if (dst.Info.Format.IsUint())
{
debugGroupName += "UInt";
_pipeline.SetProgram(_programColorBlitU);
}
else
{
debugGroupName += "Float";
_pipeline.SetProgram(_programColorBlitF);
}
}
int dstWidth = dst.Width;
int dstHeight = dst.Height;
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[16];
scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
_pipeline.SetRenderTargets([dst], null);
_pipeline.SetScissors(scissors);
_pipeline.SetClearLoadAction(clear);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
// Cleanup
if (clear)
{
_pipeline.SetClearLoadAction(false);
}
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void BlitDepthStencil(
CommandBufferScoped cbs,
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion)
{
_pipeline.SwapState(_helperShaderState);
const int RegionBufferSize = 16;
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / (float)src.Width;
region[1] = srcRegion.X2 / (float)src.Width;
region[2] = srcRegion.Y1 / (float)src.Height;
region[3] = srcRegion.Y2 / (float)src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
using var buffer = _renderer.BufferManager.ReserveOrCreate(cbs, RegionBufferSize);
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[16];
var rect = new Rectangle<float>(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
int dstWidth = dst.Width;
int dstHeight = dst.Height;
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[16];
scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
_pipeline.SetRenderTargets([], dst);
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
if (src.Info.Format is
Format.D16Unorm or
Format.D32Float or
Format.X8UintD24Unorm or
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or
Format.S8UintD24Unorm)
{
var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth);
BlitDepthStencilDraw(depthTexture, isDepth: true);
if (depthTexture != src)
{
depthTexture.Release();
}
}
if (src.Info.Format is
Format.S8Uint or
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or
Format.S8UintD24Unorm)
{
var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil);
BlitDepthStencilDraw(stencilTexture, isDepth: false);
if (stencilTexture != src)
{
stencilTexture.Release();
}
}
// Restore previous state
_pipeline.SwapState(null);
}
private static Texture CreateDepthOrStencilView(Texture depthStencilTexture, DepthStencilMode depthStencilMode)
{
if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode)
{
return depthStencilTexture;
}
return (Texture)depthStencilTexture.CreateView(new TextureCreateInfo(
depthStencilTexture.Info.Width,
depthStencilTexture.Info.Height,
depthStencilTexture.Info.Depth,
depthStencilTexture.Info.Levels,
depthStencilTexture.Info.Samples,
depthStencilTexture.Info.BlockWidth,
depthStencilTexture.Info.BlockHeight,
depthStencilTexture.Info.BytesPerPixel,
depthStencilTexture.Info.Format,
depthStencilMode,
depthStencilTexture.Info.Target,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha), 0, 0);
}
private void BlitDepthStencilDraw(Texture src, bool isDepth)
{
// TODO: Check this https://github.com/Ryujinx/Ryujinx/pull/5003/
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest);
string debugGroupName;
if (isDepth)
{
debugGroupName = "Depth Blit";
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
else
{
debugGroupName = "Stencil Blit";
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
}
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
if (isDepth)
{
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
}
else
{
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
}
}
public unsafe void DrawTexture(
ITexture src,
ISampler srcSampler,
Extents2DF srcRegion,
Extents2DF dstRegion)
{
// Save current state
var state = _pipeline.SavePredrawState();
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetStencilTest(new StencilTestDescriptor());
_pipeline.SetDepthTest(new DepthTestDescriptor());
const int RegionBufferSize = 16;
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
region[0] = srcRegion.X1 / src.Width;
region[1] = srcRegion.X2 / src.Width;
region[2] = srcRegion.Y1 / src.Height;
region[3] = srcRegion.Y2 / src.Height;
if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}
if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}
var bufferHandle = _renderer.BufferManager.CreateWithHandle(RegionBufferSize);
_renderer.BufferManager.SetData<float>(bufferHandle, 0, region);
_pipeline.SetUniformBuffers([new BufferAssignment(0, new BufferRange(bufferHandle, 0, RegionBufferSize))]);
Span<Viewport> viewports = stackalloc Viewport[16];
var rect = new Rectangle<float>(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
_pipeline.SetProgram(_programColorBlitF);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, "Draw Texture");
_renderer.BufferManager.Delete(bufferHandle);
// Restore previous state
_pipeline.RestorePredrawState(state);
}
public void ConvertI8ToI16(CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
{
ChangeStride(cbs, src, dst, srcOffset, size, 1, 2);
}
public unsafe void ChangeStride(
CommandBufferScoped cbs,
BufferHolder src,
BufferHolder dst,
int srcOffset,
int size,
int stride,
int newStride)
{
int elems = size / stride;
var srcBuffer = src.GetBuffer();
var dstBuffer = dst.GetBuffer();
const int ParamsBufferSize = 4 * sizeof(int);
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
shaderParams[0] = stride;
shaderParams[1] = newStride;
shaderParams[2] = size;
shaderParams[3] = srcOffset;
using var buffer = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
buffer.Holder.SetDataUnchecked<int>(buffer.Offset, shaderParams);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetProgram(_programStrideChange);
_pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1, "Change Stride");
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void ConvertD32S8ToD24S8(CommandBufferScoped cbs, BufferHolder src, Auto<DisposableBuffer> dstBuffer, int pixelCount, int dstOffset)
{
int inSize = pixelCount * 2 * sizeof(int);
var srcBuffer = src.GetBuffer();
const int ParamsBufferSize = sizeof(int) * 2;
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[2];
shaderParams[0] = pixelCount;
shaderParams[1] = dstOffset;
using var buffer = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
buffer.Holder.SetDataUnchecked<int>(buffer.Offset, shaderParams);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetProgram(_programConvertD32S8ToD24S8);
_pipeline.DispatchCompute(1 + inSize / ConvertElementsPerWorkgroup, 1, 1, "D32S8 to D24S8 Conversion");
// Restore previous state
_pipeline.SwapState(null);
}
public void ConvertIndexBuffer(
CommandBufferScoped cbs,
BufferHolder src,
BufferHolder dst,
IndexBufferPattern pattern,
int indexSize,
int srcOffset,
int indexCount)
{
// TODO: Support conversion with primitive restart enabled.
int primitiveCount = pattern.GetPrimitiveCount(indexCount);
int outputIndexSize = 4;
var srcBuffer = src.GetBuffer();
var dstBuffer = dst.GetBuffer();
const int ParamsBufferSize = 16 * sizeof(int);
// Save current state
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
shaderParams[8] = pattern.PrimitiveVertices;
shaderParams[9] = pattern.PrimitiveVerticesOut;
shaderParams[10] = indexSize;
shaderParams[11] = outputIndexSize;
shaderParams[12] = pattern.BaseIndex;
shaderParams[13] = pattern.IndexStride;
shaderParams[14] = srcOffset;
shaderParams[15] = primitiveCount;
pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]);
using var patternScoped = _renderer.BufferManager.ReserveOrCreate(cbs, ParamsBufferSize);
patternScoped.Holder.SetDataUnchecked<int>(patternScoped.Offset, shaderParams);
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBuffer;
sbRanges[1] = dstBuffer;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetStorageBuffers([new BufferAssignment(3, patternScoped.Range)]);
_pipeline.SetProgram(_programConvertIndexBuffer);
_pipeline.DispatchCompute(BitUtils.DivRoundUp(primitiveCount, 16), 1, 1, "Convert Index Buffer");
// Restore previous state
_pipeline.SwapState(null);
}
public unsafe void ClearColor(
int index,
ReadOnlySpan<float> clearColor,
uint componentMask,
int dstWidth,
int dstHeight,
Format format)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
// Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index);
const int ClearColorBufferSize = 16;
// TODO: Flush
using var buffer = _renderer.BufferManager.ReserveOrCreate(_pipeline.Cbs, ClearColorBufferSize);
buffer.Holder.SetDataUnchecked(buffer.Offset, clearColor);
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[16];
// TODO: Set exact viewport!
viewports[0] = new Viewport(
new Rectangle<float>(0, 0, dstWidth, dstHeight),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<uint> componentMasks = stackalloc uint[index + 1];
componentMasks[index] = componentMask;
var debugGroupName = "Clear Color ";
if (format.IsSint())
{
debugGroupName += "Int";
_pipeline.SetProgram(_programsColorClearI[index]);
}
else if (format.IsUint())
{
debugGroupName += "UInt";
_pipeline.SetProgram(_programsColorClearU[index]);
}
else
{
debugGroupName += "Float";
_pipeline.SetProgram(_programsColorClearF[index]);
}
_pipeline.SetBlendState(index, new BlendDescriptor());
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetRenderTargetColorMasks(componentMasks);
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0, debugGroupName);
// Restore previous state
_pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save);
}
public unsafe void ClearDepthStencil(
float depthValue,
bool depthMask,
int stencilValue,
int stencilMask,
int dstWidth,
int dstHeight)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
var helperScissors = _helperShaderState.Scissors;
// Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true);
const int ClearDepthBufferSize = 16;
using var buffer = _renderer.BufferManager.ReserveOrCreate(_pipeline.Cbs, ClearDepthBufferSize);
buffer.Holder.SetDataUnchecked(buffer.Offset, new ReadOnlySpan<float>(ref depthValue));
_pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]);
Span<Viewport> viewports = stackalloc Viewport[1];
viewports[0] = new Viewport(
new Rectangle<float>(0, 0, dstWidth, dstHeight),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
_pipeline.SetProgram(_programDepthStencilClear);
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetViewports(viewports);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always));
_pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask));
_pipeline.Draw(4, 1, 0, 0, "Clear Depth Stencil");
// Cleanup
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
// Restore previous state
_pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save);
}
private static StencilTestDescriptor CreateStencilTestDescriptor(
bool enabled,
int refValue = 0,
int compareMask = 0xff,
int writeMask = 0xff)
{
return new StencilTestDescriptor(
enabled,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask);
}
public void Dispose()
{
_programColorBlitF.Dispose();
_programColorBlitI.Dispose();
_programColorBlitU.Dispose();
_programColorBlitMsF.Dispose();
_programColorBlitMsI.Dispose();
_programColorBlitMsU.Dispose();
foreach (var programColorClear in _programsColorClearF)
{
programColorClear.Dispose();
}
foreach (var programColorClear in _programsColorClearU)
{
programColorClear.Dispose();
}
foreach (var programColorClear in _programsColorClearI)
{
programColorClear.Dispose();
}
_programDepthStencilClear.Dispose();
_pipeline.Dispose();
_samplerLinear.Dispose();
_samplerNearest.Dispose();
}
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Metal
{
class IdList<T> where T : class
{
private readonly List<T> _list;
private int _freeMin;
public IdList()
{
_list = new List<T>();
_freeMin = 0;
}
public int Add(T value)
{
int id;
int count = _list.Count;
id = _list.IndexOf(null, _freeMin);
if ((uint)id < (uint)count)
{
_list[id] = value;
}
else
{
id = count;
_freeMin = id + 1;
_list.Add(value);
}
return id + 1;
}
public void Remove(int id)
{
id--;
int count = _list.Count;
if ((uint)id >= (uint)count)
{
return;
}
if (id + 1 == count)
{
// Trim unused items.
int removeIndex = id;
while (removeIndex > 0 && _list[removeIndex - 1] == null)
{
removeIndex--;
}
_list.RemoveRange(removeIndex, count - removeIndex);
if (_freeMin > removeIndex)
{
_freeMin = removeIndex;
}
}
else
{
_list[id] = null;
if (_freeMin > id)
{
_freeMin = id;
}
}
}
public bool TryGetValue(int id, out T value)
{
id--;
try
{
if ((uint)id < (uint)_list.Count)
{
value = _list[id];
return value != null;
}
value = null;
return false;
}
catch (ArgumentOutOfRangeException)
{
value = null;
return false;
}
catch (IndexOutOfRangeException)
{
value = null;
return false;
}
}
public void Clear()
{
_list.Clear();
_freeMin = 0;
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _list.Count; i++)
{
if (_list[i] != null)
{
yield return _list[i];
}
}
}
}
}

View File

@ -0,0 +1,74 @@
using Ryujinx.Graphics.GAL;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class ImageArray : IImageArray
{
private readonly TextureRef[] _textureRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly bool _isBuffer;
private readonly Pipeline _pipeline;
public ImageArray(int size, bool isBuffer, Pipeline pipeline)
{
if (isBuffer)
{
_bufferTextureRefs = new TextureBuffer[size];
}
else
{
_textureRefs = new TextureRef[size];
}
_isBuffer = isBuffer;
_pipeline = pipeline;
}
public void SetImages(int index, ITexture[] images)
{
for (int i = 0; i < images.Length; i++)
{
ITexture image = images[i];
if (image is TextureBuffer textureBuffer)
{
_bufferTextureRefs[index + i] = textureBuffer;
}
else if (image is Texture texture)
{
_textureRefs[index + i].Storage = texture;
}
else if (!_isBuffer)
{
_textureRefs[index + i].Storage = null;
}
else
{
_bufferTextureRefs[index + i] = null;
}
}
SetDirty();
}
public TextureRef[] GetTextureRefs()
{
return _textureRefs;
}
public TextureBuffer[] GetBufferTextureRefs()
{
return _bufferTextureRefs;
}
private void SetDirty()
{
_pipeline.DirtyImages();
}
public void Dispose() { }
}
}

View File

@ -0,0 +1,118 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class IndexBufferPattern : IDisposable
{
public int PrimitiveVertices { get; }
public int PrimitiveVerticesOut { get; }
public int BaseIndex { get; }
public int[] OffsetIndex { get; }
public int IndexStride { get; }
public bool RepeatStart { get; }
private readonly MetalRenderer _renderer;
private int _currentSize;
private BufferHandle _repeatingBuffer;
public IndexBufferPattern(MetalRenderer renderer,
int primitiveVertices,
int primitiveVerticesOut,
int baseIndex,
int[] offsetIndex,
int indexStride,
bool repeatStart)
{
PrimitiveVertices = primitiveVertices;
PrimitiveVerticesOut = primitiveVerticesOut;
BaseIndex = baseIndex;
OffsetIndex = offsetIndex;
IndexStride = indexStride;
RepeatStart = repeatStart;
_renderer = renderer;
}
public int GetPrimitiveCount(int vertexCount)
{
return Math.Max(0, (vertexCount - BaseIndex) / IndexStride);
}
public int GetConvertedCount(int indexCount)
{
int primitiveCount = GetPrimitiveCount(indexCount);
return primitiveCount * OffsetIndex.Length;
}
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
{
int primitiveCount = GetPrimitiveCount(vertexCount);
indexCount = primitiveCount * PrimitiveVerticesOut;
int expectedSize = primitiveCount * OffsetIndex.Length;
if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null)
{
return _repeatingBuffer;
}
// Expand the repeating pattern to the number of requested primitives.
BufferHandle newBuffer = _renderer.BufferManager.CreateWithHandle(expectedSize * sizeof(int));
// Copy the old data to the new one.
if (_repeatingBuffer != BufferHandle.Null)
{
_renderer.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
_renderer.BufferManager.Delete(_repeatingBuffer);
}
_repeatingBuffer = newBuffer;
// Add the additional repeats on top.
int newPrimitives = primitiveCount;
int oldPrimitives = (_currentSize) / OffsetIndex.Length;
int[] newData;
newPrimitives -= oldPrimitives;
newData = new int[expectedSize - _currentSize];
int outOffset = 0;
int index = oldPrimitives * IndexStride + BaseIndex;
for (int i = 0; i < newPrimitives; i++)
{
if (RepeatStart)
{
// Used for triangle fan
newData[outOffset++] = 0;
}
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
{
newData[outOffset++] = index + OffsetIndex[j];
}
index += IndexStride;
}
_renderer.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
_currentSize = expectedSize;
return newBuffer;
}
public void Dispose()
{
if (_repeatingBuffer != BufferHandle.Null)
{
_renderer.BufferManager.Delete(_repeatingBuffer);
_repeatingBuffer = BufferHandle.Null;
}
}
}
}

View File

@ -0,0 +1,103 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly internal struct IndexBufferState
{
public static IndexBufferState Null => new(BufferHandle.Null, 0, 0);
private readonly int _offset;
private readonly int _size;
private readonly IndexType _type;
private readonly BufferHandle _handle;
public IndexBufferState(BufferHandle handle, int offset, int size, IndexType type = IndexType.UInt)
{
_handle = handle;
_offset = offset;
_size = size;
_type = type;
}
public (MTLBuffer, int, MTLIndexType) GetIndexBuffer(MetalRenderer renderer, CommandBufferScoped cbs)
{
Auto<DisposableBuffer> autoBuffer;
int offset, size;
MTLIndexType type;
if (_type == IndexType.UByte)
{
// Index type is not supported. Convert to I16.
autoBuffer = renderer.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size);
type = MTLIndexType.UInt16;
offset = 0;
size = _size * 2;
}
else
{
autoBuffer = renderer.BufferManager.GetBuffer(_handle, false, out int bufferSize);
if (_offset >= bufferSize)
{
autoBuffer = null;
}
type = _type.Convert();
offset = _offset;
size = _size;
}
if (autoBuffer != null)
{
DisposableBuffer buffer = autoBuffer.Get(cbs, offset, size);
return (buffer.Value, offset, type);
}
return (new MTLBuffer(IntPtr.Zero), 0, MTLIndexType.UInt16);
}
public (MTLBuffer, int, MTLIndexType) GetConvertedIndexBuffer(
MetalRenderer renderer,
CommandBufferScoped cbs,
int firstIndex,
int indexCount,
int convertedCount,
IndexBufferPattern pattern)
{
// Convert the index buffer using the given pattern.
int indexSize = GetIndexSize();
int firstIndexOffset = firstIndex * indexSize;
var autoBuffer = renderer.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize);
int size = convertedCount * 4;
if (autoBuffer != null)
{
DisposableBuffer buffer = autoBuffer.Get(cbs, 0, size);
return (buffer.Value, 0, MTLIndexType.UInt32);
}
return (new MTLBuffer(IntPtr.Zero), 0, MTLIndexType.UInt32);
}
private int GetIndexSize()
{
return _type switch
{
IndexType.UInt => 4,
IndexType.UShort => 2,
_ => 1,
};
}
}
}

View File

@ -0,0 +1,309 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
public sealed class MetalRenderer : IRenderer
{
public const int TotalSets = 4;
private readonly MTLDevice _device;
private readonly MTLCommandQueue _queue;
private readonly Func<CAMetalLayer> _getMetalLayer;
private Pipeline _pipeline;
private Window _window;
public uint ProgramCount { get; set; } = 0;
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
public bool PreferThreading => true;
public IPipeline Pipeline => _pipeline;
public IWindow Window => _window;
internal MTLCommandQueue BackgroundQueue { get; private set; }
internal HelperShader HelperShader { get; private set; }
internal BufferManager BufferManager { get; private set; }
internal CommandBufferPool CommandBufferPool { get; private set; }
internal BackgroundResources BackgroundResources { get; private set; }
internal Action<Action> InterruptAction { get; private set; }
internal SyncManager SyncManager { get; private set; }
internal HashSet<Program> Programs { get; }
internal HashSet<SamplerHolder> Samplers { get; }
public MetalRenderer(Func<CAMetalLayer> metalLayer)
{
_device = MTLDevice.CreateSystemDefaultDevice();
Programs = new HashSet<Program>();
Samplers = new HashSet<SamplerHolder>();
if (_device.ArgumentBuffersSupport != MTLArgumentBuffersTier.Tier2)
{
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
}
_queue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers + 1);
BackgroundQueue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers);
_getMetalLayer = metalLayer;
}
public void Initialize(GraphicsDebugLevel logLevel)
{
var layer = _getMetalLayer();
layer.Device = _device;
layer.FramebufferOnly = false;
CommandBufferPool = new CommandBufferPool(_queue);
_window = new Window(this, layer);
_pipeline = new Pipeline(_device, this);
BufferManager = new BufferManager(_device, this, _pipeline);
_pipeline.InitEncoderStateManager(BufferManager);
BackgroundResources = new BackgroundResources(this);
HelperShader = new HelperShader(_device, this, _pipeline);
SyncManager = new SyncManager(this);
}
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
{
// GetData methods should be thread safe, so we can call this directly.
// Texture copy (scaled) may also happen in here, so that should also be thread safe.
action();
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
return BufferManager.CreateWithHandle(size);
}
public BufferHandle CreateBuffer(IntPtr pointer, int size)
{
return BufferManager.Create(pointer, size);
}
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
{
throw new NotImplementedException();
}
public IImageArray CreateImageArray(int size, bool isBuffer)
{
return new ImageArray(size, isBuffer, _pipeline);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
ProgramCount++;
return new Program(this, _device, shaders, info.ResourceLayout, info.ComputeLocalSize);
}
public ISampler CreateSampler(SamplerCreateInfo info)
{
return new SamplerHolder(this, _device, info);
}
public ITexture CreateTexture(TextureCreateInfo info)
{
if (info.Target == Target.TextureBuffer)
{
return new TextureBuffer(_device, this, _pipeline, info);
}
return new Texture(_device, this, _pipeline, info);
}
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
return new TextureArray(size, isBuffer, _pipeline);
}
public bool PrepareHostMapping(IntPtr address, ulong size)
{
// TODO: Metal Host Mapping
return false;
}
public void CreateSync(ulong id, bool strict)
{
SyncManager.Create(id, strict);
}
public void DeleteBuffer(BufferHandle buffer)
{
BufferManager.Delete(buffer);
}
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
return BufferManager.GetData(buffer, offset, size);
}
public Capabilities GetCapabilities()
{
// TODO: Finalize these values
return new Capabilities(
api: TargetApi.Metal,
vendorName: HardwareInfoTools.GetVendor(),
SystemMemoryType.UnifiedMemory,
hasFrontFacingBug: false,
hasVectorIndexingBug: false,
needsFragmentOutputSpecialization: true,
reduceShaderPrecision: true,
supportsAstcCompression: true,
supportsBc123Compression: true,
supportsBc45Compression: true,
supportsBc67Compression: true,
supportsEtc2Compression: true,
supports3DTextureCompression: true,
supportsBgraFormat: true,
supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
supportsScaledVertexFormats: false,
supportsSnormBufferTextureFormat: true,
supportsSparseBuffer: false,
supports5BitComponentFormat: true,
supportsBlendEquationAdvanced: false,
supportsFragmentShaderInterlock: true,
supportsFragmentShaderOrderingIntel: false,
supportsGeometryShader: false,
supportsGeometryShaderPassthrough: false,
supportsTransformFeedback: false,
supportsImageLoadFormatted: false,
supportsLayerVertexTessellation: false,
supportsMismatchingViewFormat: true,
supportsCubemapView: true,
supportsNonConstantTextureOffset: false,
supportsQuads: false,
supportsSeparateSampler: true,
supportsShaderBallot: false,
supportsShaderBarrierDivergence: false,
supportsShaderFloat64: false,
supportsTextureGatherOffsets: false,
supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: false,
supportsViewportIndexVertexTessellation: false,
supportsViewportMask: false,
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: false,
uniformBufferSetIndex: (int)Constants.ConstantBuffersSetIndex,
storageBufferSetIndex: (int)Constants.StorageBuffersSetIndex,
textureSetIndex: (int)Constants.TexturesSetIndex,
imageSetIndex: (int)Constants.ImagesSetIndex,
extraSetBaseIndex: TotalSets,
maximumExtraSets: (int)Constants.MaximumExtraSets,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
maximumImagesPerStage: Constants.MaxImagesPerStage,
maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength,
maximumSupportedAnisotropy: 16,
shaderSubgroupSize: 256,
storageBufferOffsetAlignment: 16,
textureBufferOffsetAlignment: 16,
gatherBiasPrecision: 0,
maximumGpuMemory: 0
);
}
public ulong GetCurrentSync()
{
return SyncManager.GetCurrent();
}
public HardwareInfo GetHardwareInfo()
{
return new HardwareInfo(HardwareInfoTools.GetVendor(), HardwareInfoTools.GetModel(), "Apple");
}
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
throw new NotImplementedException();
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
BufferManager.SetData(buffer, offset, data, _pipeline.Cbs);
}
public void UpdateCounters()
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void PreFrame()
{
SyncManager.Cleanup();
}
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
var counterEvent = new CounterEvent();
resultHandler?.Invoke(counterEvent, type == CounterType.SamplesPassed ? (ulong)1 : 0);
return counterEvent;
}
public void ResetCounter(CounterType type)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void WaitSync(ulong id)
{
SyncManager.Wait(id);
}
public void FlushAllCommands()
{
_pipeline.FlushCommandsImpl();
}
public void RegisterFlush()
{
SyncManager.RegisterFlush();
// Periodically free unused regions of the staging buffer to avoid doing it all at once.
BufferManager.StagingBuffer.FreeCompleted();
}
public void SetInterruptAction(Action<Action> interruptAction)
{
InterruptAction = interruptAction;
}
public void Screenshot()
{
// TODO: Screenshots
}
public void Dispose()
{
BackgroundResources.Dispose();
foreach (var program in Programs)
{
program.Dispose();
}
foreach (var sampler in Samplers)
{
sampler.Dispose();
}
_pipeline.Dispose();
_window.Dispose();
}
}
}

View File

@ -0,0 +1,262 @@
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
/// <summary>
/// Holder for multiple host GPU fences.
/// </summary>
[SupportedOSPlatform("macos")]
class MultiFenceHolder
{
private const int BufferUsageTrackingGranularity = 4096;
private readonly FenceHolder[] _fences;
private readonly BufferUsageBitmap _bufferUsageBitmap;
/// <summary>
/// Creates a new instance of the multiple fence holder.
/// </summary>
public MultiFenceHolder()
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
}
/// <summary>
/// Creates a new instance of the multiple fence holder, with a given buffer size in mind.
/// </summary>
/// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size)
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
/// <summary>
/// Adds read/write buffer usage information to the uses list.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <param name="write">Whether the access is a write or not</param>
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
if (write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
}
}
/// <summary>
/// Removes all buffer usage information for a given command buffer.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
public void RemoveBufferUses(int cbIndex)
{
_bufferUsageBitmap?.Clear(cbIndex);
}
/// <summary>
/// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <returns>True if in use, false otherwise</returns>
public bool IsBufferRangeInUse(int cbIndex, int offset, int size)
{
return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size);
}
/// <summary>
/// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU.
/// </summary>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <param name="write">True if only write usages should count</param>
/// <returns>True if in use, false otherwise</returns>
public bool IsBufferRangeInUse(int offset, int size, bool write)
{
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
}
/// <summary>
/// Adds a fence to the holder.
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
/// <param name="fence">Fence to be added</param>
/// <returns>True if the command buffer's previous fence value was null</returns>
public bool AddFence(int cbIndex, FenceHolder fence)
{
ref FenceHolder fenceRef = ref _fences[cbIndex];
if (fenceRef == null)
{
fenceRef = fence;
return true;
}
return false;
}
/// <summary>
/// Removes a fence from the holder.
/// </summary>
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
public void RemoveFence(int cbIndex)
{
_fences[cbIndex] = null;
}
/// <summary>
/// Determines if a fence referenced on the given command buffer.
/// </summary>
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
/// <returns>True if referenced, false otherwise</returns>
public bool HasFence(int cbIndex)
{
return _fences[cbIndex] != null;
}
/// <summary>
/// Wait until all the fences on the holder are signaled.
/// </summary>
public void WaitForFences()
{
WaitForFencesImpl(0, 0, true);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
/// <param name="offset">Start offset of the buffer range</param>
/// <param name="size">Size of the buffer range in bytes</param>
public void WaitForFences(int offset, int size)
{
WaitForFencesImpl(offset, size, true);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
// TODO: Add a proper timeout!
public bool WaitForFences(bool indefinite)
{
return WaitForFencesImpl(0, 0, indefinite);
}
/// <summary>
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
/// </summary>
/// <param name="offset">Start offset of the buffer range</param>
/// <param name="size">Size of the buffer range in bytes</param>
/// <param name="indefinite">Indicates if this should wait indefinitely</param>
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
private bool WaitForFencesImpl(int offset, int size, bool indefinite)
{
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span<MTLCommandBuffer> fences = stackalloc MTLCommandBuffer[count];
int fenceCount = 0;
for (int i = 0; i < count; i++)
{
if (fenceHolders[i].TryGet(out MTLCommandBuffer fence))
{
fences[fenceCount] = fence;
if (fenceCount < i)
{
fenceHolders[fenceCount] = fenceHolders[i];
}
fenceCount++;
}
}
if (fenceCount == 0)
{
return true;
}
bool signaled = true;
if (indefinite)
{
foreach (var fence in fences)
{
fence.WaitUntilCompleted();
}
}
else
{
foreach (var fence in fences)
{
if (fence.Status != MTLCommandBufferStatus.Completed)
{
signaled = false;
}
}
}
for (int i = 0; i < fenceCount; i++)
{
fenceHolders[i].Put();
}
return signaled;
}
/// <summary>
/// Gets fences to wait for.
/// </summary>
/// <param name="storage">Span to store fences in</param>
/// <returns>Number of fences placed in storage</returns>
private int GetFences(Span<FenceHolder> storage)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
var fence = _fences[i];
if (fence != null)
{
storage[count++] = fence;
}
}
return count;
}
/// <summary>
/// Gets fences to wait for use of a given buffer region.
/// </summary>
/// <param name="storage">Span to store overlapping fences in</param>
/// <param name="offset">Offset of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>Number of fences for the specified region placed in storage</returns>
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
var fence = _fences[i];
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
{
storage[count++] = fence;
}
}
return count;
}
}
}

View File

@ -0,0 +1,99 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class PersistentFlushBuffer : IDisposable
{
private readonly MetalRenderer _renderer;
private BufferHolder _flushStorage;
public PersistentFlushBuffer(MetalRenderer renderer)
{
_renderer = renderer;
}
private BufferHolder ResizeIfNeeded(int size)
{
var flushStorage = _flushStorage;
if (flushStorage == null || size > _flushStorage.Size)
{
flushStorage?.Dispose();
flushStorage = _renderer.BufferManager.Create(size);
_flushStorage = flushStorage;
}
return flushStorage;
}
public Span<byte> GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size)
{
var flushStorage = ResizeIfNeeded(size);
Auto<DisposableBuffer> srcBuffer;
using (var cbs = cbp.Rent())
{
srcBuffer = buffer.GetBuffer();
var dstBuffer = flushStorage.GetBuffer();
if (srcBuffer.TryIncrementReferenceCount())
{
BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false);
}
else
{
// Source buffer is no longer alive, don't copy anything to flush storage.
srcBuffer = null;
}
}
flushStorage.WaitForFences();
srcBuffer?.DecrementReferenceCount();
return flushStorage.GetDataStorage(0, size);
}
public Span<byte> GetTextureData(CommandBufferPool cbp, Texture view, int size)
{
TextureCreateInfo info = view.Info;
var flushStorage = ResizeIfNeeded(size);
using (var cbs = cbp.Rent())
{
var buffer = flushStorage.GetBuffer().Get(cbs).Value;
var image = view.GetHandle();
view.CopyFromOrToBuffer(cbs, buffer, image, size, true, 0, 0, info.GetLayers(), info.Levels, singleSlice: false);
}
flushStorage.WaitForFences();
return flushStorage.GetDataStorage(0, size);
}
public Span<byte> GetTextureData(CommandBufferPool cbp, Texture view, int size, int layer, int level)
{
var flushStorage = ResizeIfNeeded(size);
using (var cbs = cbp.Rent())
{
var buffer = flushStorage.GetBuffer().Get(cbs).Value;
var image = view.GetHandle();
view.CopyFromOrToBuffer(cbs, buffer, image, size, true, layer, level, 1, 1, singleSlice: true);
}
flushStorage.WaitForFences();
return flushStorage.GetDataStorage(0, size);
}
public void Dispose()
{
_flushStorage.Dispose();
}
}
}

View File

@ -0,0 +1,877 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
public enum EncoderType
{
Blit,
Compute,
Render,
None
}
[SupportedOSPlatform("macos")]
class Pipeline : IPipeline, IEncoderFactory, IDisposable
{
private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB
private readonly MTLDevice _device;
private readonly MetalRenderer _renderer;
private EncoderStateManager _encoderStateManager;
private ulong _byteWeight;
public MTLCommandBuffer CommandBuffer;
public IndexBufferPattern QuadsToTrisPattern;
public IndexBufferPattern TriFanToTrisPattern;
internal CommandBufferScoped? PreloadCbs { get; private set; }
internal CommandBufferScoped Cbs { get; private set; }
internal CommandBufferEncoder Encoders => Cbs.Encoders;
internal EncoderType CurrentEncoderType => Encoders.CurrentEncoderType;
public Pipeline(MTLDevice device, MetalRenderer renderer)
{
_device = device;
_renderer = renderer;
renderer.CommandBufferPool.Initialize(this);
CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer;
}
internal void InitEncoderStateManager(BufferManager bufferManager)
{
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
QuadsToTrisPattern = new IndexBufferPattern(_renderer, 4, 6, 0, [0, 1, 2, 0, 2, 3], 4, false);
TriFanToTrisPattern = new IndexBufferPattern(_renderer, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
}
public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All, bool endRenderPass = true)
{
if (endRenderPass && CurrentEncoderType == EncoderType.Render)
{
EndCurrentPass();
}
return _encoderStateManager.SwapState(state, flags);
}
public PredrawState SavePredrawState()
{
return _encoderStateManager.SavePredrawState();
}
public void RestorePredrawState(PredrawState state)
{
_encoderStateManager.RestorePredrawState(state);
}
public void SetClearLoadAction(bool clear)
{
_encoderStateManager.SetClearLoadAction(clear);
}
public MTLRenderCommandEncoder GetOrCreateRenderEncoder(bool forDraw = false)
{
// Mark all state as dirty to ensure it is set on the new encoder
if (Cbs.Encoders.CurrentEncoderType != EncoderType.Render)
{
_encoderStateManager.SignalRenderDirty();
}
if (forDraw)
{
_encoderStateManager.RenderResourcesPrepass();
}
MTLRenderCommandEncoder renderCommandEncoder = Cbs.Encoders.EnsureRenderEncoder();
if (forDraw)
{
_encoderStateManager.RebindRenderState(renderCommandEncoder);
}
return renderCommandEncoder;
}
public MTLBlitCommandEncoder GetOrCreateBlitEncoder()
{
return Cbs.Encoders.EnsureBlitEncoder();
}
public MTLComputeCommandEncoder GetOrCreateComputeEncoder(bool forDispatch = false)
{
// Mark all state as dirty to ensure it is set on the new encoder
if (Cbs.Encoders.CurrentEncoderType != EncoderType.Compute)
{
_encoderStateManager.SignalComputeDirty();
}
if (forDispatch)
{
_encoderStateManager.ComputeResourcesPrepass();
}
MTLComputeCommandEncoder computeCommandEncoder = Cbs.Encoders.EnsureComputeEncoder();
if (forDispatch)
{
_encoderStateManager.RebindComputeState(computeCommandEncoder);
}
return computeCommandEncoder;
}
public void EndCurrentPass()
{
Cbs.Encoders.EndCurrentPass();
}
public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{
return _encoderStateManager.CreateRenderCommandEncoder();
}
public MTLComputeCommandEncoder CreateComputeCommandEncoder()
{
return _encoderStateManager.CreateComputeCommandEncoder();
}
public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear)
{
// TODO: Clean this up
var textureInfo = new TextureCreateInfo((int)drawable.Texture.Width, (int)drawable.Texture.Height, (int)drawable.Texture.Depth, (int)drawable.Texture.MipmapLevelCount, (int)drawable.Texture.SampleCount, 0, 0, 0, Format.B8G8R8A8Unorm, 0, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha);
var dst = new Texture(_device, _renderer, this, textureInfo, drawable.Texture, 0, 0);
_renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, isLinear, true);
EndCurrentPass();
Cbs.CommandBuffer.PresentDrawable(drawable);
FlushCommandsImpl();
// TODO: Auto flush counting
_renderer.SyncManager.GetAndResetWaitTicks();
// Cleanup
dst.Dispose();
}
public CommandBufferScoped GetPreloadCommandBuffer()
{
PreloadCbs ??= _renderer.CommandBufferPool.Rent();
return PreloadCbs.Value;
}
public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight)
{
bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs);
if (PreloadCbs != null && !usedByCurrentCb)
{
usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value);
}
if (usedByCurrentCb)
{
// Since we can only free memory after the command buffer that uses a given resource was executed,
// keeping the command buffer might cause a high amount of memory to be in use.
// To prevent that, we force submit command buffers if the memory usage by resources
// in use by the current command buffer is above a given limit, and those resources were disposed.
_byteWeight += byteWeight;
if (_byteWeight >= MinByteWeightForFlush)
{
FlushCommandsImpl();
}
}
}
public void FlushCommandsImpl()
{
EndCurrentPass();
_byteWeight = 0;
if (PreloadCbs != null)
{
PreloadCbs.Value.Dispose();
PreloadCbs = null;
}
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
_renderer.RegisterFlush();
}
public void DirtyTextures()
{
_encoderStateManager.DirtyTextures();
}
public void DirtyImages()
{
_encoderStateManager.DirtyImages();
}
public void Blit(
Texture src,
Texture dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool isDepthOrStencil,
bool linearFilter)
{
if (isDepthOrStencil)
{
_renderer.HelperShader.BlitDepthStencil(Cbs, src, dst, srcRegion, dstRegion);
}
else
{
_renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, linearFilter);
}
}
public void Barrier()
{
switch (CurrentEncoderType)
{
case EncoderType.Render:
{
var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;
MTLRenderStages stages = MTLRenderStages.RenderStageVertex | MTLRenderStages.RenderStageFragment;
Encoders.RenderEncoder.MemoryBarrier(scope, stages, stages);
break;
}
case EncoderType.Compute:
{
var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;
Encoders.ComputeEncoder.MemoryBarrier(scope);
break;
}
}
}
public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
{
var blitCommandEncoder = GetOrCreateBlitEncoder();
var mtlBuffer = _renderer.BufferManager.GetBuffer(destination, offset, size, true).Get(Cbs, offset, size, true).Value;
// Might need a closer look, range's count, lower, and upper bound
// must be a multiple of 4
blitCommandEncoder.FillBuffer(mtlBuffer,
new NSRange
{
location = (ulong)offset,
length = (ulong)size
},
(byte)value);
}
public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color)
{
float[] colors = [color.Red, color.Green, color.Blue, color.Alpha];
var dst = _encoderStateManager.RenderTargets[index];
// TODO: Remove workaround for Wonder which has an invalid texture due to unsupported format
if (dst == null)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Attempted to clear invalid render target!");
return;
}
_renderer.HelperShader.ClearColor(index, colors, componentMask, dst.Width, dst.Height, dst.Info.Format);
}
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{
var depthStencil = _encoderStateManager.DepthStencil;
if (depthStencil == null)
{
return;
}
_renderer.HelperShader.ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask, depthStencil.Width, depthStencil.Height);
}
public void CommandBufferBarrier()
{
Barrier();
}
public void CopyBuffer(BufferHandle src, BufferHandle dst, int srcOffset, int dstOffset, int size)
{
var srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false);
var dstBuffer = _renderer.BufferManager.GetBuffer(dst, dstOffset, size, true);
BufferHolder.Copy(Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size);
}
public void PushDebugGroup(string name)
{
var encoder = Encoders.CurrentEncoder;
var debugGroupName = StringHelper.NSString(name);
if (encoder == null)
{
return;
}
switch (Encoders.CurrentEncoderType)
{
case EncoderType.Render:
encoder.Value.PushDebugGroup(debugGroupName);
break;
case EncoderType.Blit:
encoder.Value.PushDebugGroup(debugGroupName);
break;
case EncoderType.Compute:
encoder.Value.PushDebugGroup(debugGroupName);
break;
}
}
public void PopDebugGroup()
{
var encoder = Encoders.CurrentEncoder;
if (encoder == null)
{
return;
}
switch (Encoders.CurrentEncoderType)
{
case EncoderType.Render:
encoder.Value.PopDebugGroup();
break;
case EncoderType.Blit:
encoder.Value.PopDebugGroup();
break;
case EncoderType.Compute:
encoder.Value.PopDebugGroup();
break;
}
}
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
{
DispatchCompute(groupsX, groupsY, groupsZ, String.Empty);
}
public void DispatchCompute(int groupsX, int groupsY, int groupsZ, string debugGroupName)
{
var computeCommandEncoder = GetOrCreateComputeEncoder(true);
ComputeSize localSize = _encoderStateManager.ComputeLocalSize;
if (debugGroupName != String.Empty)
{
PushDebugGroup(debugGroupName);
}
computeCommandEncoder.DispatchThreadgroups(
new MTLSize { width = (ulong)groupsX, height = (ulong)groupsY, depth = (ulong)groupsZ },
new MTLSize { width = (ulong)localSize.X, height = (ulong)localSize.Y, depth = (ulong)localSize.Z });
if (debugGroupName != String.Empty)
{
PopDebugGroup();
}
}
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
{
Draw(vertexCount, instanceCount, firstVertex, firstInstance, String.Empty);
}
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance, string debugGroupName)
{
if (vertexCount == 0)
{
return;
}
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
if (TopologyUnsupported(_encoderStateManager.Topology))
{
var pattern = GetIndexBufferPattern();
BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
var buffer = _renderer.BufferManager.GetBuffer(handle, false);
var mtlBuffer = buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value;
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
(ulong)indexCount,
MTLIndexType.UInt32,
mtlBuffer,
0);
}
else
{
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
if (debugGroupName != String.Empty)
{
PushDebugGroup(debugGroupName);
}
renderCommandEncoder.DrawPrimitives(
primitiveType,
(ulong)firstVertex,
(ulong)vertexCount,
(ulong)instanceCount,
(ulong)firstInstance);
if (debugGroupName != String.Empty)
{
PopDebugGroup();
}
}
}
private IndexBufferPattern GetIndexBufferPattern()
{
return _encoderStateManager.Topology switch
{
PrimitiveTopology.Quads => QuadsToTrisPattern,
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_encoderStateManager.Topology}"),
};
}
private PrimitiveTopology TopologyRemap(PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Quads => PrimitiveTopology.Triangles,
PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip,
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => PrimitiveTopology.Triangles,
_ => topology,
};
}
private bool TopologyUnsupported(PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Quads or PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => true,
_ => false,
};
}
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
{
if (indexCount == 0)
{
return;
}
MTLBuffer mtlBuffer;
int offset;
MTLIndexType type;
int finalIndexCount = indexCount;
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
if (TopologyUnsupported(_encoderStateManager.Topology))
{
var pattern = GetIndexBufferPattern();
int convertedCount = pattern.GetConvertedCount(indexCount);
finalIndexCount = convertedCount;
(mtlBuffer, offset, type) = _encoderStateManager.IndexBuffer.GetConvertedIndexBuffer(_renderer, Cbs, firstIndex, indexCount, convertedCount, pattern);
}
else
{
(mtlBuffer, offset, type) = _encoderStateManager.IndexBuffer.GetIndexBuffer(_renderer, Cbs);
}
if (mtlBuffer.NativePtr != IntPtr.Zero)
{
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
(ulong)finalIndexCount,
type,
mtlBuffer,
(ulong)offset,
(ulong)instanceCount,
firstVertex,
(ulong)firstInstance);
}
}
public void DrawIndexedIndirect(BufferRange indirectBuffer)
{
DrawIndexedIndirectOffset(indirectBuffer);
}
public void DrawIndexedIndirectOffset(BufferRange indirectBuffer, int offset = 0)
{
// TODO: Reindex unsupported topologies
if (TopologyUnsupported(_encoderStateManager.Topology))
{
Logger.Warning?.Print(LogClass.Gpu, $"Drawing indexed with unsupported topology: {_encoderStateManager.Topology}");
}
var buffer = _renderer.BufferManager
.GetBuffer(indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
(MTLBuffer indexBuffer, int indexOffset, MTLIndexType type) = _encoderStateManager.IndexBuffer.GetIndexBuffer(_renderer, Cbs);
if (indexBuffer.NativePtr != IntPtr.Zero && buffer.NativePtr != IntPtr.Zero)
{
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
type,
indexBuffer,
(ulong)indexOffset,
buffer,
(ulong)(indirectBuffer.Offset + offset));
}
}
public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
for (int i = 0; i < maxDrawCount; i++)
{
DrawIndexedIndirectOffset(indirectBuffer, stride * i);
}
}
public void DrawIndirect(BufferRange indirectBuffer)
{
DrawIndirectOffset(indirectBuffer);
}
public void DrawIndirectOffset(BufferRange indirectBuffer, int offset = 0)
{
if (TopologyUnsupported(_encoderStateManager.Topology))
{
// TODO: Reindex unsupported topologies
Logger.Warning?.Print(LogClass.Gpu, $"Drawing indirect with unsupported topology: {_encoderStateManager.Topology}");
}
var buffer = _renderer.BufferManager
.GetBuffer(indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
renderCommandEncoder.DrawPrimitives(
primitiveType,
buffer,
(ulong)(indirectBuffer.Offset + offset));
}
public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
for (int i = 0; i < maxDrawCount; i++)
{
DrawIndirectOffset(indirectBuffer, stride * i);
}
}
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
{
_renderer.HelperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
}
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{
// This is currently handled using shader specialization, as Metal does not support alpha test.
// In the future, we may want to use this to write the reference value into the support buffer,
// to avoid creating one version of the shader per reference value used.
}
public void SetBlendState(AdvancedBlendDescriptor blend)
{
// Metal does not support advanced blend.
}
public void SetBlendState(int index, BlendDescriptor blend)
{
_encoderStateManager.UpdateBlendDescriptors(index, blend);
}
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
{
if (enables == 0)
{
_encoderStateManager.UpdateDepthBias(0, 0, 0);
}
else
{
_encoderStateManager.UpdateDepthBias(units, factor, clamp);
}
}
public void SetDepthClamp(bool clamp)
{
_encoderStateManager.UpdateDepthClamp(clamp);
}
public void SetDepthMode(DepthMode mode)
{
// Metal does not support depth clip control.
}
public void SetDepthTest(DepthTestDescriptor depthTest)
{
_encoderStateManager.UpdateDepthState(depthTest);
}
public void SetFaceCulling(bool enable, Face face)
{
_encoderStateManager.UpdateCullMode(enable, face);
}
public void SetFrontFace(FrontFace frontFace)
{
_encoderStateManager.UpdateFrontFace(frontFace);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_encoderStateManager.UpdateIndexBuffer(buffer, type);
}
public void SetImage(ShaderStage stage, int binding, ITexture image)
{
if (image is TextureBase img)
{
_encoderStateManager.UpdateImage(stage, binding, img);
}
}
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
if (array is ImageArray imageArray)
{
_encoderStateManager.UpdateImageArray(stage, binding, imageArray);
}
}
public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
{
if (array is ImageArray imageArray)
{
_encoderStateManager.UpdateImageArraySeparate(stage, setIndex, imageArray);
}
}
public void SetLineParameters(float width, bool smooth)
{
// Metal does not support wide-lines.
}
public void SetLogicOpState(bool enable, LogicalOp op)
{
_encoderStateManager.UpdateLogicOpState(enable, op);
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
_encoderStateManager.UpdateMultisampleState(multisample);
}
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
{
// Metal does not support polygon mode.
}
public void SetPrimitiveRestart(bool enable, int index)
{
// Always active for LineStrip and TriangleStrip
// https://github.com/gpuweb/gpuweb/issues/1220#issuecomment-732483263
// https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515520-drawindexedprimitives
// https://stackoverflow.com/questions/70813665/how-to-render-multiple-trianglestrips-using-metal
// Emulating disabling this is very difficult. It's unlikely for an index buffer to use the largest possible index,
// so it's fine nearly all of the time.
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
{
_encoderStateManager.UpdatePrimitiveTopology(topology);
}
public void SetProgram(IProgram program)
{
_encoderStateManager.UpdateProgram(program);
}
public void SetRasterizerDiscard(bool discard)
{
_encoderStateManager.UpdateRasterizerDiscard(discard);
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
{
_encoderStateManager.UpdateRenderTargetColorMasks(componentMask);
}
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
{
_encoderStateManager.UpdateRenderTargets(colors, depthStencil);
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
{
_encoderStateManager.UpdateScissors(regions);
}
public void SetStencilTest(StencilTestDescriptor stencilTest)
{
_encoderStateManager.UpdateStencilState(stencilTest);
}
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateUniformBuffers(buffers);
}
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateStorageBuffers(buffers);
}
internal void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
{
_encoderStateManager.UpdateStorageBuffers(first, buffers);
}
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
{
if (texture is TextureBase tex)
{
if (sampler == null || sampler is SamplerHolder)
{
_encoderStateManager.UpdateTextureAndSampler(stage, binding, tex, (SamplerHolder)sampler);
}
}
}
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
if (array is TextureArray textureArray)
{
_encoderStateManager.UpdateTextureArray(stage, binding, textureArray);
}
}
public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
{
if (array is TextureArray textureArray)
{
_encoderStateManager.UpdateTextureArraySeparate(stage, setIndex, textureArray);
}
}
public void SetUserClipDistance(int index, bool enableClip)
{
// TODO. Same as Vulkan
}
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{
_encoderStateManager.UpdateVertexAttribs(vertexAttribs);
}
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
{
_encoderStateManager.UpdateVertexBuffers(vertexBuffers);
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
{
_encoderStateManager.UpdateViewports(viewports);
}
public void TextureBarrier()
{
if (CurrentEncoderType == EncoderType.Render)
{
Encoders.RenderEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment);
}
}
public void TextureBarrierTiled()
{
TextureBarrier();
}
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public void EndHostConditionalRendering()
{
// TODO: Implementable via indirect draw commands
}
public void BeginTransformFeedback(PrimitiveTopology topology)
{
// Metal does not support transform feedback.
}
public void EndTransformFeedback()
{
// Metal does not support transform feedback.
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{
// Metal does not support transform feedback.
}
public void Dispose()
{
EndCurrentPass();
_encoderStateManager.Dispose();
}
}
}

View File

@ -0,0 +1,286 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Program : IProgram
{
private ProgramLinkStatus _status;
private readonly ShaderSource[] _shaders;
private readonly GCHandle[] _handles;
private int _successCount;
private readonly MetalRenderer _renderer;
public MTLFunction VertexFunction;
public MTLFunction FragmentFunction;
public MTLFunction ComputeFunction;
public ComputeSize ComputeLocalSize { get; }
private HashTableSlim<PipelineUid, MTLRenderPipelineState> _graphicsPipelineCache;
private MTLComputePipelineState? _computePipelineCache;
private bool _firstBackgroundUse;
public ResourceBindingSegment[][] BindingSegments { get; }
// Argument buffer sizes for Vertex or Compute stages
public int[] ArgumentBufferSizes { get; }
// Argument buffer sizes for Fragment stage
public int[] FragArgumentBufferSizes { get; }
public Program(
MetalRenderer renderer,
MTLDevice device,
ShaderSource[] shaders,
ResourceLayout resourceLayout,
ComputeSize computeLocalSize = default)
{
_renderer = renderer;
renderer.Programs.Add(this);
ComputeLocalSize = computeLocalSize;
_shaders = shaders;
_handles = new GCHandle[_shaders.Length];
_status = ProgramLinkStatus.Incomplete;
for (int i = 0; i < _shaders.Length; i++)
{
ShaderSource shader = _shaders[i];
using var compileOptions = new MTLCompileOptions
{
PreserveInvariance = true,
LanguageVersion = MTLLanguageVersion.Version31,
};
var index = i;
_handles[i] = device.NewLibrary(StringHelper.NSString(shader.Code), compileOptions, (library, error) => CompilationResultHandler(library, error, index));
}
(BindingSegments, ArgumentBufferSizes, FragArgumentBufferSizes) = BuildBindingSegments(resourceLayout.SetUsages);
}
public void CompilationResultHandler(MTLLibrary library, NSError error, int index)
{
var shader = _shaders[index];
if (_handles[index].IsAllocated)
{
_handles[index].Free();
}
if (error != IntPtr.Zero)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, shader.Code);
Logger.Warning?.Print(LogClass.Gpu, $"{shader.Stage} shader linking failed: \n{StringHelper.String(error.LocalizedDescription)}");
_status = ProgramLinkStatus.Failure;
return;
}
switch (shader.Stage)
{
case ShaderStage.Compute:
ComputeFunction = library.NewFunction(StringHelper.NSString("kernelMain"));
break;
case ShaderStage.Vertex:
VertexFunction = library.NewFunction(StringHelper.NSString("vertexMain"));
break;
case ShaderStage.Fragment:
FragmentFunction = library.NewFunction(StringHelper.NSString("fragmentMain"));
break;
default:
Logger.Warning?.Print(LogClass.Gpu, $"Cannot handle stage {shader.Stage}!");
break;
}
_successCount++;
if (_successCount >= _shaders.Length && _status != ProgramLinkStatus.Failure)
{
_status = ProgramLinkStatus.Success;
}
}
private static (ResourceBindingSegment[][], int[], int[]) BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
int[] argBufferSizes = new int[setUsages.Count];
int[] fragArgBufferSizes = new int[setUsages.Count];
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
{
List<ResourceBindingSegment> currentSegments = new();
ResourceUsage currentUsage = default;
int currentCount = 0;
for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
{
ResourceUsage usage = setUsages[setIndex].Usages[index];
if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages ||
currentUsage.ArrayLength > 1 ||
usage.ArrayLength > 1)
{
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentUsage.Binding,
currentCount,
currentUsage.Type,
currentUsage.Stages,
currentUsage.ArrayLength > 1));
var size = currentCount * ResourcePointerSize(currentUsage.Type);
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
{
fragArgBufferSizes[setIndex] += size;
}
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
{
argBufferSizes[setIndex] += size;
}
}
currentUsage = usage;
currentCount = usage.ArrayLength;
}
else
{
currentCount++;
}
}
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentUsage.Binding,
currentCount,
currentUsage.Type,
currentUsage.Stages,
currentUsage.ArrayLength > 1));
var size = currentCount * ResourcePointerSize(currentUsage.Type);
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
{
fragArgBufferSizes[setIndex] += size;
}
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
{
argBufferSizes[setIndex] += size;
}
}
segments[setIndex] = currentSegments.ToArray();
}
return (segments, argBufferSizes, fragArgBufferSizes);
}
private static int ResourcePointerSize(ResourceType type)
{
return (type == ResourceType.TextureAndSampler ? 2 : 1);
}
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
if (blocking)
{
while (_status == ProgramLinkStatus.Incomplete)
{ }
return _status;
}
return _status;
}
public byte[] GetBinary()
{
return [];
}
public void AddGraphicsPipeline(ref PipelineUid key, MTLRenderPipelineState pipeline)
{
(_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
}
public void AddComputePipeline(MTLComputePipelineState pipeline)
{
_computePipelineCache = pipeline;
}
public bool TryGetGraphicsPipeline(ref PipelineUid key, out MTLRenderPipelineState pipeline)
{
if (_graphicsPipelineCache == null)
{
pipeline = default;
return false;
}
if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
{
if (_firstBackgroundUse)
{
Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
_firstBackgroundUse = false;
}
return false;
}
_firstBackgroundUse = false;
return true;
}
public bool TryGetComputePipeline(out MTLComputePipelineState pipeline)
{
if (_computePipelineCache.HasValue)
{
pipeline = _computePipelineCache.Value;
return true;
}
pipeline = default;
return false;
}
public void Dispose()
{
if (!_renderer.Programs.Remove(this))
{
return;
}
if (_graphicsPipelineCache != null)
{
foreach (MTLRenderPipelineState pipeline in _graphicsPipelineCache.Values)
{
pipeline.Dispose();
}
}
_computePipelineCache?.Dispose();
VertexFunction.Dispose();
FragmentFunction.Dispose();
ComputeFunction.Dispose();
}
}
}

View File

@ -0,0 +1,22 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Metal
{
readonly struct ResourceBindingSegment
{
public readonly int Binding;
public readonly int Count;
public readonly ResourceType Type;
public readonly ResourceStages Stages;
public readonly bool IsArray;
public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray)
{
Binding = binding;
Count = count;
Type = type;
Stages = stages;
IsArray = isArray;
}
}
}

View File

@ -0,0 +1,59 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class ResourceLayoutBuilder
{
private const int TotalSets = MetalRenderer.TotalSets;
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
public ResourceLayoutBuilder()
{
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
_resourceUsages = new List<ResourceUsage>[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
_resourceDescriptors[index] = new();
_resourceUsages[index] = new();
}
}
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
{
uint setIndex = type switch
{
ResourceType.UniformBuffer => Constants.ConstantBuffersSetIndex,
ResourceType.StorageBuffer => Constants.StorageBuffersSetIndex,
ResourceType.TextureAndSampler or ResourceType.BufferTexture => Constants.TexturesSetIndex,
ResourceType.Image or ResourceType.BufferImage => Constants.ImagesSetIndex,
_ => throw new ArgumentException($"Invalid resource type \"{type}\"."),
};
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
return this;
}
public ResourceLayout Build()
{
var descriptors = new ResourceDescriptorCollection[TotalSets];
var usages = new ResourceUsageCollection[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
}
return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
}
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Shaders\Blit.metal" />
<EmbeddedResource Include="Shaders\BlitMs.metal" />
<EmbeddedResource Include="Shaders\ChangeBufferStride.metal" />
<EmbeddedResource Include="Shaders\ConvertD32S8ToD24S8.metal" />
<EmbeddedResource Include="Shaders\ConvertIndexBuffer.metal" />
<EmbeddedResource Include="Shaders\ColorClear.metal" />
<EmbeddedResource Include="Shaders\DepthStencilClear.metal" />
<EmbeddedResource Include="Shaders\DepthBlit.metal" />
<EmbeddedResource Include="Shaders\DepthBlitMs.metal" />
<EmbeddedResource Include="Shaders\StencilBlit.metal" />
<EmbeddedResource Include="Shaders\StencilBlitMs.metal" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,90 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class SamplerHolder : ISampler
{
private readonly MetalRenderer _renderer;
private readonly Auto<DisposableSampler> _sampler;
public SamplerHolder(MetalRenderer renderer, MTLDevice device, SamplerCreateInfo info)
{
_renderer = renderer;
renderer.Samplers.Add(this);
(MTLSamplerMinMagFilter minFilter, MTLSamplerMipFilter mipFilter) = info.MinFilter.Convert();
MTLSamplerBorderColor borderColor = GetConstrainedBorderColor(info.BorderColor, out _);
using var descriptor = new MTLSamplerDescriptor
{
BorderColor = borderColor,
MinFilter = minFilter,
MagFilter = info.MagFilter.Convert(),
MipFilter = mipFilter,
CompareFunction = info.CompareOp.Convert(),
LodMinClamp = info.MinLod,
LodMaxClamp = info.MaxLod,
LodAverage = false,
MaxAnisotropy = Math.Max((uint)info.MaxAnisotropy, 1),
SAddressMode = info.AddressU.Convert(),
TAddressMode = info.AddressV.Convert(),
RAddressMode = info.AddressP.Convert(),
SupportArgumentBuffers = true
};
var sampler = device.NewSamplerState(descriptor);
_sampler = new Auto<DisposableSampler>(new DisposableSampler(sampler));
}
private static MTLSamplerBorderColor GetConstrainedBorderColor(ColorF arbitraryBorderColor, out bool cantConstrain)
{
float r = arbitraryBorderColor.Red;
float g = arbitraryBorderColor.Green;
float b = arbitraryBorderColor.Blue;
float a = arbitraryBorderColor.Alpha;
if (r == 0f && g == 0f && b == 0f)
{
if (a == 1f)
{
cantConstrain = false;
return MTLSamplerBorderColor.OpaqueBlack;
}
if (a == 0f)
{
cantConstrain = false;
return MTLSamplerBorderColor.TransparentBlack;
}
}
else if (r == 1f && g == 1f && b == 1f && a == 1f)
{
cantConstrain = false;
return MTLSamplerBorderColor.OpaqueWhite;
}
cantConstrain = true;
return MTLSamplerBorderColor.OpaqueBlack;
}
public Auto<DisposableSampler> GetSampler()
{
return _sampler;
}
public void Dispose()
{
if (_renderer.Samplers.Remove(this))
{
_sampler.Dispose();
}
}
}
}

View File

@ -0,0 +1,43 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct TexCoords {
float data[4];
};
struct ConstantBuffers {
constant TexCoords* tex_coord;
};
struct Textures
{
texture2d<FORMAT, access::sample> texture;
sampler sampler;
};
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
CopyVertexOut out;
int low = vid & 1;
int high = vid >> 1;
out.uv.x = constant_buffers.tex_coord->data[low];
out.uv.y = constant_buffers.tex_coord->data[2 + high];
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
fragment FORMAT4 fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]]) {
return textures.texture.sample(textures.sampler, in.uv);
}

View File

@ -0,0 +1,45 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct TexCoords {
float data[4];
};
struct ConstantBuffers {
constant TexCoords* tex_coord;
};
struct Textures
{
texture2d_ms<FORMAT, access::read> texture;
};
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
CopyVertexOut out;
int low = vid & 1;
int high = vid >> 1;
out.uv.x = constant_buffers.tex_coord->data[low];
out.uv.y = constant_buffers.tex_coord->data[2 + high];
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
fragment FORMAT4 fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]],
uint sample_id [[sample_id]]) {
uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height());
uint2 tex_coord = uint2(in.uv * float2(tex_size));
return textures.texture.read(tex_coord, sample_id);
}

View File

@ -0,0 +1,72 @@
#include <metal_stdlib>
using namespace metal;
struct StrideArguments {
int4 data;
};
struct InData {
uint8_t data[1];
};
struct OutData {
uint8_t data[1];
};
struct ConstantBuffers {
constant StrideArguments* stride_arguments;
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
};
kernel void kernelMain(constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]],
device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]],
uint3 threads_per_threadgroup [[threads_per_threadgroup]],
uint3 threadgroups_per_grid [[threadgroups_per_grid]])
{
// Determine what slice of the stride copies this invocation will perform.
int sourceStride = constant_buffers.stride_arguments->data.x;
int targetStride = constant_buffers.stride_arguments->data.y;
int bufferSize = constant_buffers.stride_arguments->data.z;
int sourceOffset = constant_buffers.stride_arguments->data.w;
int strideRemainder = targetStride - sourceStride;
int invocations = int(threads_per_threadgroup.x * threadgroups_per_grid.x);
int copiesRequired = bufferSize / sourceStride;
// Find the copies that this invocation should perform.
// - Copies that all invocations perform.
int allInvocationCopies = copiesRequired / invocations;
// - Extra remainder copy that this invocation performs.
int index = int(thread_position_in_grid.x);
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
int copyCount = allInvocationCopies + extra;
// Finally, get the starting offset. Make sure to count extra copies.
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
int srcOffset = sourceOffset + startCopy * sourceStride;
int dstOffset = startCopy * targetStride;
// Perform the copies for this region
for (int i = 0; i < copyCount; i++) {
for (int j = 0; j < sourceStride; j++) {
storage_buffers.out_data->data[dstOffset++] = storage_buffers.in_data->data[srcOffset++];
}
for (int j = 0; j < strideRemainder; j++) {
storage_buffers.out_data->data[dstOffset++] = uint8_t(0);
}
}
}

View File

@ -0,0 +1,38 @@
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
};
struct ClearColor {
FORMAT4 data;
};
struct ConstantBuffers {
constant ClearColor* clear_color;
};
vertex VertexOut vertexMain(ushort vid [[vertex_id]]) {
int low = vid & 1;
int high = vid >> 1;
VertexOut out;
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
struct FragmentOut {
FORMAT4 color [[color(COLOR_ATTACHMENT_INDEX)]];
};
fragment FragmentOut fragmentMain(VertexOut in [[stage_in]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
return {constant_buffers.clear_color->data};
}

View File

@ -0,0 +1,66 @@
#include <metal_stdlib>
using namespace metal;
struct StrideArguments {
int pixelCount;
int dstStartOffset;
};
struct InData {
uint data[1];
};
struct OutData {
uint data[1];
};
struct ConstantBuffers {
constant StrideArguments* stride_arguments;
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
};
kernel void kernelMain(constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]],
device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]],
uint3 threads_per_threadgroup [[threads_per_threadgroup]],
uint3 threadgroups_per_grid [[threadgroups_per_grid]])
{
// Determine what slice of the stride copies this invocation will perform.
int invocations = int(threads_per_threadgroup.x * threadgroups_per_grid.x);
int copiesRequired = constant_buffers.stride_arguments->pixelCount;
// Find the copies that this invocation should perform.
// - Copies that all invocations perform.
int allInvocationCopies = copiesRequired / invocations;
// - Extra remainder copy that this invocation performs.
int index = int(thread_position_in_grid.x);
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
int copyCount = allInvocationCopies + extra;
// Finally, get the starting offset. Make sure to count extra copies.
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
int srcOffset = startCopy * 2;
int dstOffset = constant_buffers.stride_arguments->dstStartOffset + startCopy;
// Perform the conversion for this region.
for (int i = 0; i < copyCount; i++)
{
float depth = as_type<float>(storage_buffers.in_data->data[srcOffset++]);
uint stencil = storage_buffers.in_data->data[srcOffset++];
uint rescaledDepth = uint(clamp(depth, 0.0, 1.0) * 16777215.0);
storage_buffers.out_data->data[dstOffset++] = (rescaledDepth << 8) | (stencil & 0xff);
}
}

View File

@ -0,0 +1,59 @@
#include <metal_stdlib>
using namespace metal;
struct IndexBufferPattern {
int pattern[8];
int primitiveVertices;
int primitiveVerticesOut;
int indexSize;
int indexSizeOut;
int baseIndex;
int indexStride;
int srcOffset;
int totalPrimitives;
};
struct InData {
uint8_t data[1];
};
struct OutData {
uint8_t data[1];
};
struct StorageBuffers {
device InData* in_data;
device OutData* out_data;
constant IndexBufferPattern* index_buffer_pattern;
};
kernel void kernelMain(device StorageBuffers &storage_buffers [[buffer(STORAGE_BUFFERS_INDEX)]],
uint3 thread_position_in_grid [[thread_position_in_grid]])
{
int primitiveIndex = int(thread_position_in_grid.x);
if (primitiveIndex >= storage_buffers.index_buffer_pattern->totalPrimitives)
{
return;
}
int inOffset = primitiveIndex * storage_buffers.index_buffer_pattern->indexStride;
int outOffset = primitiveIndex * storage_buffers.index_buffer_pattern->primitiveVerticesOut;
for (int i = 0; i < storage_buffers.index_buffer_pattern->primitiveVerticesOut; i++)
{
int j;
int io = max(0, inOffset + storage_buffers.index_buffer_pattern->baseIndex + storage_buffers.index_buffer_pattern->pattern[i]) * storage_buffers.index_buffer_pattern->indexSize;
int oo = (outOffset + i) * storage_buffers.index_buffer_pattern->indexSizeOut;
for (j = 0; j < storage_buffers.index_buffer_pattern->indexSize; j++)
{
storage_buffers.out_data->data[oo + j] = storage_buffers.in_data->data[storage_buffers.index_buffer_pattern->srcOffset + io + j];
}
for(; j < storage_buffers.index_buffer_pattern->indexSizeOut; j++)
{
storage_buffers.out_data->data[oo + j] = uint8_t(0);
}
}
}

View File

@ -0,0 +1,27 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d<float, access::sample> texture;
sampler sampler;
};
struct FragmentOut {
float depth [[depth(any)]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]]) {
FragmentOut out;
out.depth = textures.texture.sample(textures.sampler, in.uv).r;
return out;
}

View File

@ -0,0 +1,29 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d_ms<float, access::read> texture;
};
struct FragmentOut {
float depth [[depth(any)]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]],
uint sample_id [[sample_id]]) {
FragmentOut out;
uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height());
uint2 tex_coord = uint2(in.uv * float2(tex_size));
out.depth = textures.texture.read(tex_coord, sample_id).r;
return out;
}

View File

@ -0,0 +1,42 @@
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
};
struct FragmentOut {
float depth [[depth(any)]];
};
struct ClearDepth {
float data;
};
struct ConstantBuffers {
constant ClearDepth* clear_depth;
};
vertex VertexOut vertexMain(ushort vid [[vertex_id]]) {
int low = vid & 1;
int high = vid >> 1;
VertexOut out;
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;
return out;
}
fragment FragmentOut fragmentMain(VertexOut in [[stage_in]],
constant ConstantBuffers &constant_buffers [[buffer(CONSTANT_BUFFERS_INDEX)]]) {
FragmentOut out;
out.depth = constant_buffers.clear_depth->data;
return out;
}

View File

@ -0,0 +1,27 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d<uint, access::sample> texture;
sampler sampler;
};
struct FragmentOut {
uint stencil [[stencil]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]]) {
FragmentOut out;
out.stencil = textures.texture.sample(textures.sampler, in.uv).r;
return out;
}

View File

@ -0,0 +1,29 @@
#include <metal_stdlib>
using namespace metal;
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
struct Textures
{
texture2d_ms<uint, access::read> texture;
};
struct FragmentOut {
uint stencil [[stencil]];
};
fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]],
constant Textures &textures [[buffer(TEXTURES_INDEX)]],
uint sample_id [[sample_id]]) {
FragmentOut out;
uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height());
uint2 tex_coord = uint2(in.uv * float2(tex_size));
out.stencil = textures.texture.read(tex_coord, sample_id).r;
return out;
}

View File

@ -0,0 +1,288 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
readonly struct StagingBufferReserved
{
public readonly BufferHolder Buffer;
public readonly int Offset;
public readonly int Size;
public StagingBufferReserved(BufferHolder buffer, int offset, int size)
{
Buffer = buffer;
Offset = offset;
Size = size;
}
}
[SupportedOSPlatform("macos")]
class StagingBuffer : IDisposable
{
private const int BufferSize = 32 * 1024 * 1024;
private int _freeOffset;
private int _freeSize;
private readonly MetalRenderer _renderer;
private readonly BufferHolder _buffer;
private readonly int _resourceAlignment;
public readonly BufferHandle Handle;
private readonly struct PendingCopy
{
public FenceHolder Fence { get; }
public int Size { get; }
public PendingCopy(FenceHolder fence, int size)
{
Fence = fence;
Size = size;
fence.Get();
}
}
private readonly Queue<PendingCopy> _pendingCopies;
public StagingBuffer(MetalRenderer renderer, BufferManager bufferManager)
{
_renderer = renderer;
Handle = bufferManager.CreateWithHandle(BufferSize, out _buffer);
_pendingCopies = new Queue<PendingCopy>();
_freeSize = BufferSize;
_resourceAlignment = Constants.MinResourceAlignment;
}
public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
{
bool isRender = cbs != null;
CommandBufferScoped scoped = cbs ?? cbp.Rent();
// Must push all data to the buffer. If it can't fit, split it up.
while (data.Length > 0)
{
if (_freeSize < data.Length)
{
FreeCompleted();
}
while (_freeSize == 0)
{
if (!WaitFreeCompleted(cbp))
{
if (isRender)
{
_renderer.FlushAllCommands();
scoped = cbp.Rent();
isRender = false;
}
else
{
scoped = cbp.ReturnAndRent(scoped);
}
}
}
int chunkSize = Math.Min(_freeSize, data.Length);
PushDataImpl(scoped, dst, dstOffset, data[..chunkSize]);
dstOffset += chunkSize;
data = data[chunkSize..];
}
if (!isRender)
{
scoped.Dispose();
}
}
private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
{
var srcBuffer = _buffer.GetBuffer();
var dstBuffer = dst.GetBuffer(dstOffset, data.Length, true);
int offset = _freeOffset;
int capacity = BufferSize - offset;
if (capacity < data.Length)
{
_buffer.SetDataUnchecked(offset, data[..capacity]);
_buffer.SetDataUnchecked(0, data[capacity..]);
BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity);
BufferHolder.Copy(cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity);
}
else
{
_buffer.SetDataUnchecked(offset, data);
BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length);
}
_freeOffset = (offset + data.Length) & (BufferSize - 1);
_freeSize -= data.Length;
Debug.Assert(_freeSize >= 0);
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length));
}
public bool TryPushData(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
{
if (data.Length > BufferSize)
{
return false;
}
if (_freeSize < data.Length)
{
FreeCompleted();
if (_freeSize < data.Length)
{
return false;
}
}
PushDataImpl(cbs, dst, dstOffset, data);
return true;
}
private StagingBufferReserved ReserveDataImpl(CommandBufferScoped cbs, int size, int alignment)
{
// Assumes the caller has already determined that there is enough space.
int offset = BitUtils.AlignUp(_freeOffset, alignment);
int padding = offset - _freeOffset;
int capacity = Math.Min(_freeSize, BufferSize - offset);
int reservedLength = size + padding;
if (capacity < size)
{
offset = 0; // Place at start.
reservedLength += capacity;
}
_freeOffset = (_freeOffset + reservedLength) & (BufferSize - 1);
_freeSize -= reservedLength;
Debug.Assert(_freeSize >= 0);
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), reservedLength));
return new StagingBufferReserved(_buffer, offset, size);
}
private int GetContiguousFreeSize(int alignment)
{
int alignedFreeOffset = BitUtils.AlignUp(_freeOffset, alignment);
int padding = alignedFreeOffset - _freeOffset;
// Free regions:
// - Aligned free offset to end (minimum free size - padding)
// - 0 to _freeOffset + freeSize wrapped (only if free area contains 0)
int endOffset = (_freeOffset + _freeSize) & (BufferSize - 1);
return Math.Max(
Math.Min(_freeSize - padding, BufferSize - alignedFreeOffset),
endOffset <= _freeOffset ? Math.Min(_freeSize, endOffset) : 0
);
}
/// <summary>
/// Reserve a range on the staging buffer for the current command buffer and upload data to it.
/// </summary>
/// <param name="cbs">Command buffer to reserve the data on</param>
/// <param name="size">The minimum size the reserved data requires</param>
/// <param name="alignment">The required alignment for the buffer offset</param>
/// <returns>The reserved range of the staging buffer</returns>
public StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size, int alignment)
{
if (size > BufferSize)
{
return null;
}
// Temporary reserved data cannot be fragmented.
if (GetContiguousFreeSize(alignment) < size)
{
FreeCompleted();
if (GetContiguousFreeSize(alignment) < size)
{
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Staging buffer out of space to reserve data of size {size}.");
return null;
}
}
return ReserveDataImpl(cbs, size, alignment);
}
/// <summary>
/// Reserve a range on the staging buffer for the current command buffer and upload data to it.
/// Uses the most permissive byte alignment.
/// </summary>
/// <param name="cbs">Command buffer to reserve the data on</param>
/// <param name="size">The minimum size the reserved data requires</param>
/// <returns>The reserved range of the staging buffer</returns>
public StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size)
{
return TryReserveData(cbs, size, _resourceAlignment);
}
private bool WaitFreeCompleted(CommandBufferPool cbp)
{
if (_pendingCopies.TryPeek(out var pc))
{
if (!pc.Fence.IsSignaled())
{
if (cbp.IsFenceOnRentedCommandBuffer(pc.Fence))
{
return false;
}
pc.Fence.Wait();
}
var dequeued = _pendingCopies.Dequeue();
Debug.Assert(dequeued.Fence == pc.Fence);
_freeSize += pc.Size;
pc.Fence.Put();
}
return true;
}
public void FreeCompleted()
{
FenceHolder signalledFence = null;
while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled()))
{
signalledFence = pc.Fence; // Already checked - don't need to do it again.
var dequeued = _pendingCopies.Dequeue();
Debug.Assert(dequeued.Fence == pc.Fence);
_freeSize += pc.Size;
pc.Fence.Put();
}
}
public void Dispose()
{
_renderer.BufferManager.Delete(Handle);
while (_pendingCopies.TryDequeue(out var pc))
{
pc.Fence.Put();
}
}
}
}

View File

@ -0,0 +1,110 @@
using SharpMetal.Metal;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Metal.State
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct StencilUid
{
public uint ReadMask;
public uint WriteMask;
public ushort Operations;
public MTLStencilOperation StencilFailureOperation
{
readonly get => (MTLStencilOperation)((Operations >> 0) & 0xF);
set => Operations = (ushort)((Operations & 0xFFF0) | ((int)value << 0));
}
public MTLStencilOperation DepthFailureOperation
{
readonly get => (MTLStencilOperation)((Operations >> 4) & 0xF);
set => Operations = (ushort)((Operations & 0xFF0F) | ((int)value << 4));
}
public MTLStencilOperation DepthStencilPassOperation
{
readonly get => (MTLStencilOperation)((Operations >> 8) & 0xF);
set => Operations = (ushort)((Operations & 0xF0FF) | ((int)value << 8));
}
public MTLCompareFunction StencilCompareFunction
{
readonly get => (MTLCompareFunction)((Operations >> 12) & 0xF);
set => Operations = (ushort)((Operations & 0x0FFF) | ((int)value << 12));
}
}
[StructLayout(LayoutKind.Explicit, Size = 24)]
internal struct DepthStencilUid : IEquatable<DepthStencilUid>
{
[FieldOffset(0)]
public StencilUid FrontFace;
[FieldOffset(10)]
public ushort DepthState;
[FieldOffset(12)]
public StencilUid BackFace;
[FieldOffset(22)]
private readonly ushort _padding;
// Quick access aliases
#pragma warning disable IDE0044 // Add readonly modifier
[FieldOffset(0)]
private ulong _id0;
[FieldOffset(8)]
private ulong _id1;
[FieldOffset(0)]
private Vector128<byte> _id01;
[FieldOffset(16)]
private ulong _id2;
#pragma warning restore IDE0044 // Add readonly modifier
public MTLCompareFunction DepthCompareFunction
{
readonly get => (MTLCompareFunction)((DepthState >> 0) & 0xF);
set => DepthState = (ushort)((DepthState & 0xFFF0) | ((int)value << 0));
}
public bool StencilTestEnabled
{
readonly get => ((DepthState >> 4) & 0x1) != 0;
set => DepthState = (ushort)((DepthState & 0xFFEF) | ((value ? 1 : 0) << 4));
}
public bool DepthWriteEnabled
{
readonly get => ((DepthState >> 15) & 0x1) != 0;
set => DepthState = (ushort)((DepthState & 0x7FFF) | ((value ? 1 : 0) << 15));
}
public readonly override bool Equals(object obj)
{
return obj is DepthStencilUid other && EqualsRef(ref other);
}
public readonly bool EqualsRef(ref DepthStencilUid other)
{
return _id01.Equals(other._id01) && _id2 == other._id2;
}
public readonly bool Equals(DepthStencilUid other)
{
return EqualsRef(ref other);
}
public readonly override int GetHashCode()
{
ulong hash64 = _id0 * 23 ^
_id1 * 23 ^
_id2 * 23;
return (int)hash64 ^ ((int)(hash64 >> 32) * 17);
}
}
}

View File

@ -0,0 +1,341 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct PipelineState
{
public PipelineUid Internal;
public uint StagesCount
{
readonly get => (byte)((Internal.Id0 >> 0) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint VertexAttributeDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 8) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public uint VertexBindingDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 16) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFF00FFFF) | ((ulong)value << 16);
}
public uint ColorBlendAttachmentStateCount
{
readonly get => (byte)((Internal.Id0 >> 24) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00FFFFFF) | ((ulong)value << 24);
}
/*
* Can be an input to a pipeline, but not sure what the situation for that is.
public PrimitiveTopology Topology
{
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
*/
public MTLLogicOperation LogicOp
{
readonly get => (MTLLogicOperation)((Internal.Id0 >> 32) & 0xF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFF0FFFFFFFF) | ((ulong)value << 32);
}
//?
public bool PrimitiveRestartEnable
{
readonly get => ((Internal.Id0 >> 36) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFEFFFFFFFFF) | ((value ? 1UL : 0UL) << 36);
}
public bool RasterizerDiscardEnable
{
readonly get => ((Internal.Id0 >> 37) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFDFFFFFFFFF) | ((value ? 1UL : 0UL) << 37);
}
public bool LogicOpEnable
{
readonly get => ((Internal.Id0 >> 38) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFBFFFFFFFFF) | ((value ? 1UL : 0UL) << 38);
}
public bool AlphaToCoverageEnable
{
readonly get => ((Internal.Id0 >> 40) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFEFFFFFFFFFF) | ((value ? 1UL : 0UL) << 40);
}
public bool AlphaToOneEnable
{
readonly get => ((Internal.Id0 >> 41) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFDFFFFFFFFFF) | ((value ? 1UL : 0UL) << 41);
}
public MTLPixelFormat DepthStencilFormat
{
readonly get => (MTLPixelFormat)(Internal.Id0 >> 48);
set => Internal.Id0 = (Internal.Id0 & 0x0000FFFFFFFFFFFF) | ((ulong)value << 48);
}
// Not sure how to appropriately use this, but it does need to be passed for tess.
public uint PatchControlPoints
{
readonly get => (uint)((Internal.Id1 >> 0) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint SamplesCount
{
readonly get => (uint)((Internal.Id1 >> 32) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)value << 32);
}
// Advanced blend not supported
private readonly void BuildColorAttachment(MTLRenderPipelineColorAttachmentDescriptor descriptor, ColorBlendStateUid blendState)
{
descriptor.PixelFormat = blendState.PixelFormat;
descriptor.SetBlendingEnabled(blendState.Enable);
descriptor.AlphaBlendOperation = blendState.AlphaBlendOperation;
descriptor.RgbBlendOperation = blendState.RgbBlendOperation;
descriptor.SourceAlphaBlendFactor = blendState.SourceAlphaBlendFactor;
descriptor.DestinationAlphaBlendFactor = blendState.DestinationAlphaBlendFactor;
descriptor.SourceRGBBlendFactor = blendState.SourceRGBBlendFactor;
descriptor.DestinationRGBBlendFactor = blendState.DestinationRGBBlendFactor;
descriptor.WriteMask = blendState.WriteMask;
}
private readonly MTLVertexDescriptor BuildVertexDescriptor()
{
var vertexDescriptor = new MTLVertexDescriptor();
for (int i = 0; i < VertexAttributeDescriptionsCount; i++)
{
VertexInputAttributeUid uid = Internal.VertexAttributes[i];
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = uid.Format;
attrib.Offset = uid.Offset;
attrib.BufferIndex = uid.BufferIndex;
}
for (int i = 0; i < VertexBindingDescriptionsCount; i++)
{
VertexInputLayoutUid uid = Internal.VertexBindings[i];
var layout = vertexDescriptor.Layouts.Object((ulong)i);
layout.StepFunction = uid.StepFunction;
layout.StepRate = uid.StepRate;
layout.Stride = uid.Stride;
}
return vertexDescriptor;
}
private MTLRenderPipelineDescriptor CreateRenderDescriptor(Program program)
{
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var blendState = Internal.ColorBlendState[i];
if (blendState.PixelFormat != MTLPixelFormat.Invalid)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
BuildColorAttachment(pipelineAttachment, blendState);
}
}
MTLPixelFormat dsFormat = DepthStencilFormat;
if (dsFormat != MTLPixelFormat.Invalid)
{
switch (dsFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {dsFormat}!");
break;
}
}
renderPipelineDescriptor.LogicOperationEnabled = LogicOpEnable;
renderPipelineDescriptor.LogicOperation = LogicOp;
renderPipelineDescriptor.AlphaToCoverageEnabled = AlphaToCoverageEnable;
renderPipelineDescriptor.AlphaToOneEnabled = AlphaToOneEnable;
renderPipelineDescriptor.RasterizationEnabled = !RasterizerDiscardEnable;
renderPipelineDescriptor.SampleCount = Math.Max(1, SamplesCount);
var vertexDescriptor = BuildVertexDescriptor();
renderPipelineDescriptor.VertexDescriptor = vertexDescriptor;
renderPipelineDescriptor.VertexFunction = program.VertexFunction;
if (program.FragmentFunction.NativePtr != 0)
{
renderPipelineDescriptor.FragmentFunction = program.FragmentFunction;
}
return renderPipelineDescriptor;
}
public MTLRenderPipelineState CreateRenderPipeline(MTLDevice device, Program program)
{
if (program.TryGetGraphicsPipeline(ref Internal, out var pipelineState))
{
return pipelineState;
}
using var descriptor = CreateRenderDescriptor(program);
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewRenderPipelineState(descriptor, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddGraphicsPipeline(ref Internal, pipelineState);
return pipelineState;
}
public static MTLComputePipelineDescriptor CreateComputeDescriptor(Program program)
{
ComputeSize localSize = program.ComputeLocalSize;
uint maxThreads = (uint)(localSize.X * localSize.Y * localSize.Z);
if (maxThreads == 0)
{
throw new InvalidOperationException($"Local thread size for compute cannot be 0 in any dimension.");
}
var descriptor = new MTLComputePipelineDescriptor
{
ComputeFunction = program.ComputeFunction,
MaxTotalThreadsPerThreadgroup = maxThreads,
ThreadGroupSizeIsMultipleOfThreadExecutionWidth = true,
};
return descriptor;
}
public static MTLComputePipelineState CreateComputePipeline(MTLDevice device, Program program)
{
if (program.TryGetComputePipeline(out var pipelineState))
{
return pipelineState;
}
using MTLComputePipelineDescriptor descriptor = CreateComputeDescriptor(program);
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewComputePipelineState(descriptor, MTLPipelineOption.None, 0, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Compute Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddComputePipeline(pipelineState);
return pipelineState;
}
public void Initialize()
{
SamplesCount = 1;
Internal.ResetColorState();
}
/*
* TODO, this is from vulkan.
private void UpdateVertexAttributeDescriptions(VulkanRenderer gd)
{
// Vertex attributes exceeding the stride are invalid.
// In metal, they cause glitches with the vertex shader fetching incorrect values.
// To work around this, we reduce the format to something that doesn't exceed the stride if possible.
// The assumption is that the exceeding components are not actually accessed on the shader.
for (int index = 0; index < VertexAttributeDescriptionsCount; index++)
{
var attribute = Internal.VertexAttributeDescriptions[index];
int vbIndex = GetVertexBufferIndex(attribute.Binding);
if (vbIndex >= 0)
{
ref var vb = ref Internal.VertexBindingDescriptions[vbIndex];
Format format = attribute.Format;
while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride)
{
Format newFormat = FormatTable.DropLastComponent(format);
if (newFormat == format)
{
// That case means we failed to find a format that fits within the stride,
// so just restore the original format and give up.
format = attribute.Format;
break;
}
format = newFormat;
}
if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format))
{
attribute.Format = format;
}
}
_vertexAttributeDescriptions2[index] = attribute;
}
}
private int GetVertexBufferIndex(uint binding)
{
for (int index = 0; index < VertexBindingDescriptionsCount; index++)
{
if (Internal.VertexBindingDescriptions[index].Binding == binding)
{
return index;
}
}
return -1;
}
*/
}
}

View File

@ -0,0 +1,208 @@
using Ryujinx.Common.Memory;
using SharpMetal.Metal;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
struct VertexInputAttributeUid
{
public ulong Id0;
public ulong Offset
{
readonly get => (uint)((Id0 >> 0) & 0xFFFFFFFF);
set => Id0 = (Id0 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public MTLVertexFormat Format
{
readonly get => (MTLVertexFormat)((Id0 >> 32) & 0xFFFF);
set => Id0 = (Id0 & 0xFFFF0000FFFFFFFF) | ((ulong)value << 32);
}
public ulong BufferIndex
{
readonly get => ((Id0 >> 48) & 0xFFFF);
set => Id0 = (Id0 & 0x0000FFFFFFFFFFFF) | ((ulong)value << 48);
}
}
struct VertexInputLayoutUid
{
public ulong Id0;
public uint Stride
{
readonly get => (uint)((Id0 >> 0) & 0xFFFFFFFF);
set => Id0 = (Id0 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint StepRate
{
readonly get => (uint)((Id0 >> 32) & 0x1FFFFFFF);
set => Id0 = (Id0 & 0xE0000000FFFFFFFF) | ((ulong)value << 32);
}
public MTLVertexStepFunction StepFunction
{
readonly get => (MTLVertexStepFunction)((Id0 >> 61) & 0x7);
set => Id0 = (Id0 & 0x1FFFFFFFFFFFFFFF) | ((ulong)value << 61);
}
}
struct ColorBlendStateUid
{
public ulong Id0;
public MTLPixelFormat PixelFormat
{
readonly get => (MTLPixelFormat)((Id0 >> 0) & 0xFFFF);
set => Id0 = (Id0 & 0xFFFFFFFFFFFF0000) | ((ulong)value << 0);
}
public MTLBlendFactor SourceRGBBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 16) & 0xFF);
set => Id0 = (Id0 & 0xFFFFFFFFFF00FFFF) | ((ulong)value << 16);
}
public MTLBlendFactor DestinationRGBBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 24) & 0xFF);
set => Id0 = (Id0 & 0xFFFFFFFF00FFFFFF) | ((ulong)value << 24);
}
public MTLBlendOperation RgbBlendOperation
{
readonly get => (MTLBlendOperation)((Id0 >> 32) & 0xF);
set => Id0 = (Id0 & 0xFFFFFFF0FFFFFFFF) | ((ulong)value << 32);
}
public MTLBlendOperation AlphaBlendOperation
{
readonly get => (MTLBlendOperation)((Id0 >> 36) & 0xF);
set => Id0 = (Id0 & 0xFFFFFF0FFFFFFFFF) | ((ulong)value << 36);
}
public MTLBlendFactor SourceAlphaBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 40) & 0xFF);
set => Id0 = (Id0 & 0xFFFF00FFFFFFFFFF) | ((ulong)value << 40);
}
public MTLBlendFactor DestinationAlphaBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 48) & 0xFF);
set => Id0 = (Id0 & 0xFF00FFFFFFFFFFFF) | ((ulong)value << 48);
}
public MTLColorWriteMask WriteMask
{
readonly get => (MTLColorWriteMask)((Id0 >> 56) & 0xF);
set => Id0 = (Id0 & 0xF0FFFFFFFFFFFFFF) | ((ulong)value << 56);
}
public bool Enable
{
readonly get => ((Id0 >> 63) & 0x1) != 0UL;
set => Id0 = (Id0 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
}
public void Swap(ColorBlendStateUid uid)
{
var format = PixelFormat;
this = uid;
PixelFormat = format;
}
}
[SupportedOSPlatform("macos")]
struct PipelineUid : IRefEquatable<PipelineUid>
{
public ulong Id0;
public ulong Id1;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 8) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 16) & 0xFF);
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id0 >> 24) & 0xFF);
public Array32<VertexInputAttributeUid> VertexAttributes;
public Array33<VertexInputLayoutUid> VertexBindings;
public Array8<ColorBlendStateUid> ColorBlendState;
public uint AttachmentIntegerFormatMask;
public bool LogicOpsAllowed;
public void ResetColorState()
{
ColorBlendState = new();
for (int i = 0; i < ColorBlendState.Length; i++)
{
ColorBlendState[i].WriteMask = MTLColorWriteMask.All;
}
}
public readonly override bool Equals(object obj)
{
return obj is PipelineUid other && Equals(other);
}
public bool Equals(ref PipelineUid other)
{
if (!Unsafe.As<ulong, Vector128<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id0)))
{
return false;
}
if (!SequenceEqual<VertexInputAttributeUid>(VertexAttributes.AsSpan(), other.VertexAttributes.AsSpan(), VertexAttributeDescriptionsCount))
{
return false;
}
if (!SequenceEqual<VertexInputLayoutUid>(VertexBindings.AsSpan(), other.VertexBindings.AsSpan(), VertexBindingDescriptionsCount))
{
return false;
}
if (!SequenceEqual<ColorBlendStateUid>(ColorBlendState.AsSpan(), other.ColorBlendState.AsSpan(), ColorBlendAttachmentStateCount))
{
return false;
}
return true;
}
private static bool SequenceEqual<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, uint count) where T : unmanaged
{
return MemoryMarshal.Cast<T, byte>(x[..(int)count]).SequenceEqual(MemoryMarshal.Cast<T, byte>(y[..(int)count]));
}
public override int GetHashCode()
{
ulong hash64 = Id0 * 23 ^
Id1 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{
hash64 ^= VertexAttributes[i].Id0 * 23;
}
for (int i = 0; i < (int)VertexBindingDescriptionsCount; i++)
{
hash64 ^= VertexBindings[i].Id0 * 23;
}
for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++)
{
hash64 ^= ColorBlendState[i].Id0 * 23;
}
return (int)hash64 ^ ((int)(hash64 >> 32) * 17);
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
abstract class StateCache<T, TDescriptor, THash> : IDisposable where T : IDisposable
{
private readonly Dictionary<THash, T> _cache = new();
protected abstract THash GetHash(TDescriptor descriptor);
protected abstract T CreateValue(TDescriptor descriptor);
public void Dispose()
{
foreach (T value in _cache.Values)
{
value.Dispose();
}
GC.SuppressFinalize(this);
}
public T GetOrCreate(TDescriptor descriptor)
{
var hash = GetHash(descriptor);
if (_cache.TryGetValue(hash, out T value))
{
return value;
}
else
{
var newValue = CreateValue(descriptor);
_cache.Add(hash, newValue);
return newValue;
}
}
}
}

View File

@ -0,0 +1,30 @@
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class StringHelper
{
public static NSString NSString(string source)
{
return new(ObjectiveC.IntPtr_objc_msgSend(new ObjectiveCClass("NSString"), "stringWithUTF8String:", source));
}
public static unsafe string String(NSString source)
{
char[] sourceBuffer = new char[source.Length];
fixed (char* pSourceBuffer = sourceBuffer)
{
ObjectiveC.bool_objc_msgSend(source,
"getCString:maxLength:encoding:",
pSourceBuffer,
source.MaximumLengthOfBytes(NSStringEncoding.UTF16) + 1,
(ulong)NSStringEncoding.UTF16);
}
return new string(sourceBuffer);
}
}
}

View File

@ -0,0 +1,214 @@
using Ryujinx.Common.Logging;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class SyncManager
{
private class SyncHandle
{
public ulong ID;
public MultiFenceHolder Waitable;
public ulong FlushId;
public bool Signalled;
public bool NeedsFlush(ulong currentFlushId)
{
return (long)(FlushId - currentFlushId) >= 0;
}
}
private ulong _firstHandle;
private readonly MetalRenderer _renderer;
private readonly List<SyncHandle> _handles;
private ulong _flushId;
private long _waitTicks;
public SyncManager(MetalRenderer renderer)
{
_renderer = renderer;
_handles = new List<SyncHandle>();
}
public void RegisterFlush()
{
_flushId++;
}
public void Create(ulong id, bool strict)
{
ulong flushId = _flushId;
MultiFenceHolder waitable = new();
if (strict || _renderer.InterruptAction == null)
{
_renderer.FlushAllCommands();
_renderer.CommandBufferPool.AddWaitable(waitable);
}
else
{
// Don't flush commands, instead wait for the current command buffer to finish.
// If this sync is waited on before the command buffer is submitted, interrupt the gpu thread and flush it manually.
_renderer.CommandBufferPool.AddInUseWaitable(waitable);
}
SyncHandle handle = new()
{
ID = id,
Waitable = waitable,
FlushId = flushId,
};
lock (_handles)
{
_handles.Add(handle);
}
}
public ulong GetCurrent()
{
lock (_handles)
{
ulong lastHandle = _firstHandle;
foreach (SyncHandle handle in _handles)
{
lock (handle)
{
if (handle.Waitable == null)
{
continue;
}
if (handle.ID > lastHandle)
{
bool signaled = handle.Signalled || handle.Waitable.WaitForFences(false);
if (signaled)
{
lastHandle = handle.ID;
handle.Signalled = true;
}
}
}
}
return lastHandle;
}
}
public void Wait(ulong id)
{
SyncHandle result = null;
lock (_handles)
{
if ((long)(_firstHandle - id) > 0)
{
return; // The handle has already been signalled or deleted.
}
foreach (SyncHandle handle in _handles)
{
if (handle.ID == id)
{
result = handle;
break;
}
}
}
if (result != null)
{
if (result.Waitable == null)
{
return;
}
long beforeTicks = Stopwatch.GetTimestamp();
if (result.NeedsFlush(_flushId))
{
_renderer.InterruptAction(() =>
{
if (result.NeedsFlush(_flushId))
{
_renderer.FlushAllCommands();
}
});
}
lock (result)
{
if (result.Waitable == null)
{
return;
}
bool signaled = result.Signalled || result.Waitable.WaitForFences(true);
if (!signaled)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Metal Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
}
else
{
_waitTicks += Stopwatch.GetTimestamp() - beforeTicks;
result.Signalled = true;
}
}
}
}
public void Cleanup()
{
// Iterate through handles and remove any that have already been signalled.
while (true)
{
SyncHandle first = null;
lock (_handles)
{
first = _handles.FirstOrDefault();
}
if (first == null || first.NeedsFlush(_flushId))
{
break;
}
bool signaled = first.Waitable.WaitForFences(false);
if (signaled)
{
// Delete the sync object.
lock (_handles)
{
lock (first)
{
_firstHandle = first.ID + 1;
_handles.RemoveAt(0);
first.Waitable = null;
}
}
}
else
{
// This sync handle and any following have not been reached yet.
break;
}
}
}
public long GetAndResetWaitTicks()
{
long result = _waitTicks;
_waitTicks = 0;
return result;
}
}
}

View File

@ -0,0 +1,654 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Texture : TextureBase, ITexture
{
private MTLTexture _identitySwizzleHandle;
private readonly bool _identityIsDifferent;
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info) : base(device, renderer, pipeline, info)
{
MTLPixelFormat pixelFormat = FormatTable.GetFormat(Info.Format);
var descriptor = new MTLTextureDescriptor
{
PixelFormat = pixelFormat,
Usage = MTLTextureUsage.Unknown,
SampleCount = (ulong)Info.Samples,
TextureType = Info.Target.Convert(),
Width = (ulong)Info.Width,
Height = (ulong)Info.Height,
MipmapLevelCount = (ulong)Info.Levels
};
if (info.Target == Target.Texture3D)
{
descriptor.Depth = (ulong)Info.Depth;
}
else if (info.Target != Target.Cubemap)
{
if (info.Target == Target.CubemapArray)
{
descriptor.ArrayLength = (ulong)(Info.Depth / 6);
}
else
{
descriptor.ArrayLength = (ulong)Info.Depth;
}
}
MTLTextureSwizzleChannels swizzle = GetSwizzle(info, descriptor.PixelFormat);
_identitySwizzleHandle = Device.NewTexture(descriptor);
if (SwizzleIsIdentity(swizzle))
{
MtlTexture = _identitySwizzleHandle;
}
else
{
MtlTexture = CreateDefaultView(_identitySwizzleHandle, swizzle, descriptor);
_identityIsDifferent = true;
}
MtlFormat = pixelFormat;
descriptor.Dispose();
}
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info, MTLTexture sourceTexture, int firstLayer, int firstLevel) : base(device, renderer, pipeline, info)
{
var pixelFormat = FormatTable.GetFormat(Info.Format);
if (info.DepthStencilMode == DepthStencilMode.Stencil)
{
pixelFormat = pixelFormat switch
{
MTLPixelFormat.Depth32FloatStencil8 => MTLPixelFormat.X32Stencil8,
MTLPixelFormat.Depth24UnormStencil8 => MTLPixelFormat.X24Stencil8,
_ => pixelFormat
};
}
var textureType = Info.Target.Convert();
NSRange levels;
levels.location = (ulong)firstLevel;
levels.length = (ulong)Info.Levels;
NSRange slices;
slices.location = (ulong)firstLayer;
slices.length = textureType == MTLTextureType.Type3D ? 1 : (ulong)info.GetDepthOrLayers();
var swizzle = GetSwizzle(info, pixelFormat);
_identitySwizzleHandle = sourceTexture.NewTextureView(pixelFormat, textureType, levels, slices);
if (SwizzleIsIdentity(swizzle))
{
MtlTexture = _identitySwizzleHandle;
}
else
{
MtlTexture = sourceTexture.NewTextureView(pixelFormat, textureType, levels, slices, swizzle);
_identityIsDifferent = true;
}
MtlFormat = pixelFormat;
FirstLayer = firstLayer;
FirstLevel = firstLevel;
}
public void PopulateRenderPassAttachment(MTLRenderPassColorAttachmentDescriptor descriptor)
{
descriptor.Texture = _identitySwizzleHandle;
}
private MTLTexture CreateDefaultView(MTLTexture texture, MTLTextureSwizzleChannels swizzle, MTLTextureDescriptor descriptor)
{
NSRange levels;
levels.location = 0;
levels.length = (ulong)Info.Levels;
NSRange slices;
slices.location = 0;
slices.length = Info.Target == Target.Texture3D ? 1 : (ulong)Info.GetDepthOrLayers();
return texture.NewTextureView(descriptor.PixelFormat, descriptor.TextureType, levels, slices, swizzle);
}
private bool SwizzleIsIdentity(MTLTextureSwizzleChannels swizzle)
{
return swizzle.red == MTLTextureSwizzle.Red &&
swizzle.green == MTLTextureSwizzle.Green &&
swizzle.blue == MTLTextureSwizzle.Blue &&
swizzle.alpha == MTLTextureSwizzle.Alpha;
}
private MTLTextureSwizzleChannels GetSwizzle(TextureCreateInfo info, MTLPixelFormat pixelFormat)
{
var swizzleR = Info.SwizzleR.Convert();
var swizzleG = Info.SwizzleG.Convert();
var swizzleB = Info.SwizzleB.Convert();
var swizzleA = Info.SwizzleA.Convert();
if (info.Format == Format.R5G5B5A1Unorm ||
info.Format == Format.R5G5B5X1Unorm ||
info.Format == Format.R5G6B5Unorm)
{
(swizzleB, swizzleR) = (swizzleR, swizzleB);
}
else if (pixelFormat == MTLPixelFormat.ABGR4Unorm || info.Format == Format.A1B5G5R5Unorm)
{
var tempB = swizzleB;
var tempA = swizzleA;
swizzleB = swizzleG;
swizzleA = swizzleR;
swizzleR = tempA;
swizzleG = tempB;
}
return new MTLTextureSwizzleChannels
{
red = swizzleR,
green = swizzleG,
blue = swizzleB,
alpha = swizzleA
};
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
CommandBufferScoped cbs = Pipeline.Cbs;
TextureBase src = this;
TextureBase dst = (TextureBase)destination;
if (!Valid || !dst.Valid)
{
return;
}
var srcImage = GetHandle();
var dstImage = dst.GetHandle();
if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
{
// int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
// _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, 0, firstLayer, layers);
}
else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
{
// int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
// _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, 0, firstLayer, layers);
}
else if (dst.Info.BytesPerPixel != Info.BytesPerPixel)
{
// int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
// int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
// _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
}
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
// int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
// int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
// TODO: depth copy?
// _gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
}
else
{
TextureCopy.Copy(
cbs,
srcImage,
dstImage,
src.Info,
dst.Info,
0,
firstLayer,
0,
firstLevel);
}
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
CommandBufferScoped cbs = Pipeline.Cbs;
TextureBase src = this;
TextureBase dst = (TextureBase)destination;
if (!Valid || !dst.Valid)
{
return;
}
var srcImage = GetHandle();
var dstImage = dst.GetHandle();
if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
{
// _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
}
else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
{
// _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
}
else if (dst.Info.BytesPerPixel != Info.BytesPerPixel)
{
// _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
// _gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else
{
TextureCopy.Copy(
cbs,
srcImage,
dstImage,
src.Info,
dst.Info,
srcLayer,
dstLayer,
srcLevel,
dstLevel,
1,
1);
}
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
if (!Renderer.CommandBufferPool.OwnedByCurrentThread)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Metal doesn't currently support scaled blit on background thread.");
return;
}
var dst = (Texture)destination;
bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
Pipeline.Blit(this, dst, srcRegion, dstRegion, isDepthOrStencil, linearFilter);
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
var cbs = Pipeline.Cbs;
int outSize = Info.GetMipSize(level);
int hostSize = GetBufferDataLength(outSize);
int offset = range.Offset;
var autoBuffer = Renderer.BufferManager.GetBuffer(range.Handle, true);
var mtlBuffer = autoBuffer.Get(cbs, range.Offset, outSize).Value;
if (PrepareOutputBuffer(cbs, hostSize, mtlBuffer, out MTLBuffer copyToBuffer, out BufferHolder tempCopyHolder))
{
offset = 0;
}
CopyFromOrToBuffer(cbs, copyToBuffer, MtlTexture, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride);
if (tempCopyHolder != null)
{
CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset);
tempCopyHolder.Dispose();
}
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
return new Texture(Device, Renderer, Pipeline, info, _identitySwizzleHandle, firstLayer, firstLevel);
}
private void CopyDataToBuffer(Span<byte> storage, ReadOnlySpan<byte> input)
{
if (NeedsD24S8Conversion())
{
FormatConverter.ConvertD24S8ToD32FS8(storage, input);
return;
}
input.CopyTo(storage);
}
private ReadOnlySpan<byte> GetDataFromBuffer(ReadOnlySpan<byte> storage, int size, Span<byte> output)
{
if (NeedsD24S8Conversion())
{
if (output.IsEmpty)
{
output = new byte[GetBufferDataLength(size)];
}
FormatConverter.ConvertD32FS8ToD24S8(output, storage);
return output;
}
return storage;
}
private bool PrepareOutputBuffer(CommandBufferScoped cbs, int hostSize, MTLBuffer target, out MTLBuffer copyTarget, out BufferHolder copyTargetHolder)
{
if (NeedsD24S8Conversion())
{
copyTargetHolder = Renderer.BufferManager.Create(hostSize);
copyTarget = copyTargetHolder.GetBuffer().Get(cbs, 0, hostSize).Value;
return true;
}
copyTarget = target;
copyTargetHolder = null;
return false;
}
private void CopyDataToOutputBuffer(CommandBufferScoped cbs, BufferHolder hostData, Auto<DisposableBuffer> copyTarget, int hostSize, int dstOffset)
{
if (NeedsD24S8Conversion())
{
Renderer.HelperShader.ConvertD32S8ToD24S8(cbs, hostData, copyTarget, hostSize / (2 * sizeof(int)), dstOffset);
}
}
private bool NeedsD24S8Conversion()
{
return FormatTable.IsD24S8(Info.Format) && MtlFormat == MTLPixelFormat.Depth32FloatStencil8;
}
public void CopyFromOrToBuffer(
CommandBufferScoped cbs,
MTLBuffer buffer,
MTLTexture image,
int size,
bool to,
int dstLayer,
int dstLevel,
int dstLayers,
int dstLevels,
bool singleSlice,
int offset = 0,
int stride = 0)
{
MTLBlitCommandEncoder blitCommandEncoder = cbs.Encoders.EnsureBlitEncoder();
bool is3D = Info.Target == Target.Texture3D;
int width = Math.Max(1, Info.Width >> dstLevel);
int height = Math.Max(1, Info.Height >> dstLevel);
int depth = is3D && !singleSlice ? Math.Max(1, Info.Depth >> dstLevel) : 1;
int layers = dstLayers;
int levels = dstLevels;
for (int oLevel = 0; oLevel < levels; oLevel++)
{
int level = oLevel + dstLevel;
int mipSize = Info.GetMipSize2D(level);
int mipSizeLevel = GetBufferDataLength(is3D && !singleSlice
? Info.GetMipSize(level)
: mipSize * dstLayers);
int endOffset = offset + mipSizeLevel;
if ((uint)endOffset > (uint)size)
{
break;
}
for (int oLayer = 0; oLayer < layers; oLayer++)
{
int layer = !is3D ? dstLayer + oLayer : 0;
int z = is3D ? dstLayer + oLayer : 0;
if (to)
{
blitCommandEncoder.CopyFromTexture(
image,
(ulong)layer,
(ulong)level,
new MTLOrigin { z = (ulong)z },
new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 },
buffer,
(ulong)offset,
(ulong)Info.GetMipStride(level),
(ulong)mipSize
);
}
else
{
blitCommandEncoder.CopyFromBuffer(
buffer,
(ulong)offset,
(ulong)Info.GetMipStride(level),
(ulong)mipSize,
new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 },
image,
(ulong)(layer + oLayer),
(ulong)level,
new MTLOrigin { z = (ulong)z }
);
}
offset += mipSize;
}
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (Info.Target == Target.Texture3D)
{
depth = Math.Max(1, depth >> 1);
}
}
}
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
{
int size = 0;
for (int level = 0; level < Info.Levels; level++)
{
size += Info.GetMipSize(level);
}
size = GetBufferDataLength(size);
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size);
return GetDataFromBuffer(result, size, result);
}
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
{
int size = GetBufferDataLength(Info.GetMipSize(level));
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
return GetDataFromBuffer(result, size, result);
}
public PinnedSpan<byte> GetData()
{
BackgroundResource resources = Renderer.BackgroundResources.Get();
if (Renderer.CommandBufferPool.OwnedByCurrentThread)
{
Renderer.FlushAllCommands();
return PinnedSpan<byte>.UnsafeFromSpan(GetData(Renderer.CommandBufferPool, resources.GetFlushBuffer()));
}
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer()));
}
public PinnedSpan<byte> GetData(int layer, int level)
{
BackgroundResource resources = Renderer.BackgroundResources.Get();
if (Renderer.CommandBufferPool.OwnedByCurrentThread)
{
Renderer.FlushAllCommands();
return PinnedSpan<byte>.UnsafeFromSpan(GetData(Renderer.CommandBufferPool, resources.GetFlushBuffer(), layer, level));
}
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level));
}
public void SetData(MemoryOwner<byte> data)
{
var blitCommandEncoder = Pipeline.GetOrCreateBlitEncoder();
var dataSpan = data.Memory.Span;
var buffer = Renderer.BufferManager.Create(dataSpan.Length);
buffer.SetDataUnchecked(0, dataSpan);
var mtlBuffer = buffer.GetBuffer(false).Get(Pipeline.Cbs).Value;
int width = Info.Width;
int height = Info.Height;
int depth = Info.Depth;
int levels = Info.Levels;
int layers = Info.GetLayers();
bool is3D = Info.Target == Target.Texture3D;
int offset = 0;
for (int level = 0; level < levels; level++)
{
int mipSize = Info.GetMipSize2D(level);
int endOffset = offset + mipSize;
if ((uint)endOffset > (uint)dataSpan.Length)
{
return;
}
for (int layer = 0; layer < layers; layer++)
{
blitCommandEncoder.CopyFromBuffer(
mtlBuffer,
(ulong)offset,
(ulong)Info.GetMipStride(level),
(ulong)mipSize,
new MTLSize { width = (ulong)width, height = (ulong)height, depth = is3D ? (ulong)depth : 1 },
MtlTexture,
(ulong)layer,
(ulong)level,
new MTLOrigin()
);
offset += mipSize;
}
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (is3D)
{
depth = Math.Max(1, depth >> 1);
}
}
// Cleanup
buffer.Dispose();
}
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice)
{
int bufferDataLength = GetBufferDataLength(data.Length);
using var bufferHolder = Renderer.BufferManager.Create(bufferDataLength);
// TODO: loadInline logic
var cbs = Pipeline.Cbs;
CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data);
var buffer = bufferHolder.GetBuffer().Get(cbs).Value;
var image = GetHandle();
CopyFromOrToBuffer(cbs, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
}
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true);
data.Dispose();
}
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
var blitCommandEncoder = Pipeline.GetOrCreateBlitEncoder();
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
ulong bytesPerImage = 0;
if (MtlTexture.TextureType == MTLTextureType.Type3D)
{
bytesPerImage = bytesPerRow * (ulong)Info.Height;
}
var dataSpan = data.Memory.Span;
var buffer = Renderer.BufferManager.Create(dataSpan.Length);
buffer.SetDataUnchecked(0, dataSpan);
var mtlBuffer = buffer.GetBuffer(false).Get(Pipeline.Cbs).Value;
blitCommandEncoder.CopyFromBuffer(
mtlBuffer,
0,
bytesPerRow,
bytesPerImage,
new MTLSize { width = (ulong)region.Width, height = (ulong)region.Height, depth = 1 },
MtlTexture,
(ulong)layer,
(ulong)level,
new MTLOrigin { x = (ulong)region.X, y = (ulong)region.Y }
);
// Cleanup
buffer.Dispose();
}
private int GetBufferDataLength(int length)
{
if (NeedsD24S8Conversion())
{
return length * 2;
}
return length;
}
public void SetStorage(BufferRange buffer)
{
throw new NotImplementedException();
}
public override void Release()
{
if (_identityIsDifferent)
{
_identitySwizzleHandle.Dispose();
}
base.Release();
}
}
}

View File

@ -0,0 +1,93 @@
using Ryujinx.Graphics.GAL;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
internal class TextureArray : ITextureArray
{
private readonly TextureRef[] _textureRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly bool _isBuffer;
private readonly Pipeline _pipeline;
public TextureArray(int size, bool isBuffer, Pipeline pipeline)
{
if (isBuffer)
{
_bufferTextureRefs = new TextureBuffer[size];
}
else
{
_textureRefs = new TextureRef[size];
}
_isBuffer = isBuffer;
_pipeline = pipeline;
}
public void SetSamplers(int index, ISampler[] samplers)
{
for (int i = 0; i < samplers.Length; i++)
{
ISampler sampler = samplers[i];
if (sampler is SamplerHolder samp)
{
_textureRefs[index + i].Sampler = samp.GetSampler();
}
else
{
_textureRefs[index + i].Sampler = default;
}
}
SetDirty();
}
public void SetTextures(int index, ITexture[] textures)
{
for (int i = 0; i < textures.Length; i++)
{
ITexture texture = textures[i];
if (texture is TextureBuffer textureBuffer)
{
_bufferTextureRefs[index + i] = textureBuffer;
}
else if (texture is Texture tex)
{
_textureRefs[index + i].Storage = tex;
}
else if (!_isBuffer)
{
_textureRefs[index + i].Storage = null;
}
else
{
_bufferTextureRefs[index + i] = null;
}
}
SetDirty();
}
public TextureRef[] GetTextureRefs()
{
return _textureRefs;
}
public TextureBuffer[] GetBufferTextureRefs()
{
return _bufferTextureRefs;
}
private void SetDirty()
{
_pipeline.DirtyTextures();
}
public void Dispose() { }
}
}

View File

@ -0,0 +1,67 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
abstract class TextureBase : IDisposable
{
private int _isValid = 1;
public bool Valid => Volatile.Read(ref _isValid) != 0;
protected readonly Pipeline Pipeline;
protected readonly MTLDevice Device;
protected readonly MetalRenderer Renderer;
protected MTLTexture MtlTexture;
public readonly TextureCreateInfo Info;
public int Width => Info.Width;
public int Height => Info.Height;
public int Depth => Info.Depth;
public MTLPixelFormat MtlFormat { get; protected set; }
public int FirstLayer { get; protected set; }
public int FirstLevel { get; protected set; }
public TextureBase(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info)
{
Device = device;
Renderer = renderer;
Pipeline = pipeline;
Info = info;
}
public MTLTexture GetHandle()
{
if (_isValid == 0)
{
return new MTLTexture(IntPtr.Zero);
}
return MtlTexture;
}
public virtual void Release()
{
Dispose();
}
public void Dispose()
{
bool wasValid = Interlocked.Exchange(ref _isValid, 0) != 0;
if (wasValid)
{
if (MtlTexture != IntPtr.Zero)
{
MtlTexture.Dispose();
}
}
}
}
}

View File

@ -0,0 +1,132 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class TextureBuffer : TextureBase, ITexture
{
private MTLTextureDescriptor _descriptor;
private BufferHandle _bufferHandle;
private int _offset;
private int _size;
private int _bufferCount;
private Auto<DisposableBuffer> _buffer;
public TextureBuffer(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info) : base(device, renderer, pipeline, info)
{
MTLPixelFormat pixelFormat = FormatTable.GetFormat(Info.Format);
_descriptor = new MTLTextureDescriptor
{
PixelFormat = pixelFormat,
Usage = MTLTextureUsage.Unknown,
TextureType = MTLTextureType.TextureBuffer,
Width = (ulong)Info.Width,
Height = (ulong)Info.Height,
};
MtlFormat = pixelFormat;
}
public void RebuildStorage(bool write)
{
if (MtlTexture != IntPtr.Zero)
{
MtlTexture.Dispose();
}
if (_buffer == null)
{
MtlTexture = default;
}
else
{
DisposableBuffer buffer = _buffer.Get(Pipeline.Cbs, _offset, _size, write);
_descriptor.Width = (uint)(_size / Info.BytesPerPixel);
MtlTexture = buffer.Value.NewTexture(_descriptor, (ulong)_offset, (ulong)_size);
}
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
throw new NotSupportedException();
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public PinnedSpan<byte> GetData()
{
return Renderer.GetBufferData(_bufferHandle, _offset, _size);
}
public PinnedSpan<byte> GetData(int layer, int level)
{
return GetData();
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
throw new NotImplementedException();
}
public void SetData(MemoryOwner<byte> data)
{
Renderer.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
data.Dispose();
}
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
throw new NotSupportedException();
}
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
throw new NotSupportedException();
}
public void SetStorage(BufferRange buffer)
{
if (_bufferHandle == buffer.Handle &&
_offset == buffer.Offset &&
_size == buffer.Size &&
_bufferCount == Renderer.BufferManager.BufferCount)
{
return;
}
_bufferHandle = buffer.Handle;
_offset = buffer.Offset;
_size = buffer.Size;
_bufferCount = Renderer.BufferManager.BufferCount;
_buffer = Renderer.BufferManager.GetBuffer(_bufferHandle, false);
}
public override void Release()
{
_descriptor.Dispose();
base.Release();
}
}
}

View File

@ -0,0 +1,265 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class TextureCopy
{
public static ulong CopyFromOrToBuffer(
CommandBufferScoped cbs,
MTLBuffer buffer,
MTLTexture image,
TextureCreateInfo info,
bool to,
int dstLayer,
int dstLevel,
int x,
int y,
int width,
int height,
ulong offset = 0)
{
MTLBlitCommandEncoder blitCommandEncoder = cbs.Encoders.EnsureBlitEncoder();
bool is3D = info.Target == Target.Texture3D;
int blockWidth = BitUtils.DivRoundUp(width, info.BlockWidth);
int blockHeight = BitUtils.DivRoundUp(height, info.BlockHeight);
ulong bytesPerRow = (ulong)BitUtils.AlignUp(blockWidth * info.BytesPerPixel, 4);
ulong bytesPerImage = bytesPerRow * (ulong)blockHeight;
MTLOrigin origin = new MTLOrigin { x = (ulong)x, y = (ulong)y, z = is3D ? (ulong)dstLayer : 0 };
MTLSize region = new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 };
uint layer = is3D ? 0 : (uint)dstLayer;
if (to)
{
blitCommandEncoder.CopyFromTexture(
image,
layer,
(ulong)dstLevel,
origin,
region,
buffer,
offset,
bytesPerRow,
bytesPerImage);
}
else
{
blitCommandEncoder.CopyFromBuffer(buffer, offset, bytesPerRow, bytesPerImage, region, image, layer, (ulong)dstLevel, origin);
}
return offset + bytesPerImage;
}
public static void Copy(
CommandBufferScoped cbs,
MTLTexture srcImage,
MTLTexture dstImage,
TextureCreateInfo srcInfo,
TextureCreateInfo dstInfo,
int srcLayer,
int dstLayer,
int srcLevel,
int dstLevel)
{
int srcDepth = srcInfo.GetDepthOrLayers();
int srcLevels = srcInfo.Levels;
int dstDepth = dstInfo.GetDepthOrLayers();
int dstLevels = dstInfo.Levels;
if (dstInfo.Target == Target.Texture3D)
{
dstDepth = Math.Max(1, dstDepth >> dstLevel);
}
int depth = Math.Min(srcDepth, dstDepth);
int levels = Math.Min(srcLevels, dstLevels);
Copy(
cbs,
srcImage,
dstImage,
srcInfo,
dstInfo,
srcLayer,
dstLayer,
srcLevel,
dstLevel,
depth,
levels);
}
public static void Copy(
CommandBufferScoped cbs,
MTLTexture srcImage,
MTLTexture dstImage,
TextureCreateInfo srcInfo,
TextureCreateInfo dstInfo,
int srcDepthOrLayer,
int dstDepthOrLayer,
int srcLevel,
int dstLevel,
int depthOrLayers,
int levels)
{
MTLBlitCommandEncoder blitCommandEncoder = cbs.Encoders.EnsureBlitEncoder();
int srcZ;
int srcLayer;
int srcDepth;
int srcLayers;
if (srcInfo.Target == Target.Texture3D)
{
srcZ = srcDepthOrLayer;
srcLayer = 0;
srcDepth = depthOrLayers;
srcLayers = 1;
}
else
{
srcZ = 0;
srcLayer = srcDepthOrLayer;
srcDepth = 1;
srcLayers = depthOrLayers;
}
int dstZ;
int dstLayer;
int dstLayers;
if (dstInfo.Target == Target.Texture3D)
{
dstZ = dstDepthOrLayer;
dstLayer = 0;
dstLayers = 1;
}
else
{
dstZ = 0;
dstLayer = dstDepthOrLayer;
dstLayers = depthOrLayers;
}
int srcWidth = srcInfo.Width;
int srcHeight = srcInfo.Height;
int dstWidth = dstInfo.Width;
int dstHeight = dstInfo.Height;
srcWidth = Math.Max(1, srcWidth >> srcLevel);
srcHeight = Math.Max(1, srcHeight >> srcLevel);
dstWidth = Math.Max(1, dstWidth >> dstLevel);
dstHeight = Math.Max(1, dstHeight >> dstLevel);
int blockWidth = 1;
int blockHeight = 1;
bool sizeInBlocks = false;
MTLBuffer tempBuffer = default;
if (srcInfo.Format != dstInfo.Format && (srcInfo.IsCompressed || dstInfo.IsCompressed))
{
// Compressed alias copies need to happen through a temporary buffer.
// The data is copied from the source to the buffer, then the buffer to the destination.
// The length of the buffer should be the maximum slice size for the destination.
tempBuffer = blitCommandEncoder.Device.NewBuffer((ulong)dstInfo.GetMipSize2D(0), MTLResourceOptions.ResourceStorageModePrivate);
}
// When copying from a compressed to a non-compressed format,
// the non-compressed texture will have the size of the texture
// in blocks (not in texels), so we must adjust that size to
// match the size in texels of the compressed texture.
if (!srcInfo.IsCompressed && dstInfo.IsCompressed)
{
srcWidth *= dstInfo.BlockWidth;
srcHeight *= dstInfo.BlockHeight;
blockWidth = dstInfo.BlockWidth;
blockHeight = dstInfo.BlockHeight;
sizeInBlocks = true;
}
else if (srcInfo.IsCompressed && !dstInfo.IsCompressed)
{
dstWidth *= srcInfo.BlockWidth;
dstHeight *= srcInfo.BlockHeight;
blockWidth = srcInfo.BlockWidth;
blockHeight = srcInfo.BlockHeight;
}
int width = Math.Min(srcWidth, dstWidth);
int height = Math.Min(srcHeight, dstHeight);
for (int level = 0; level < levels; level++)
{
// Stop copy if we are already out of the levels range.
if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels)
{
break;
}
int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width;
int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height;
int layers = Math.Max(dstLayers - dstLayer, srcLayers);
for (int layer = 0; layer < layers; layer++)
{
if (tempBuffer.NativePtr != 0)
{
// Copy through the temp buffer
CopyFromOrToBuffer(cbs, tempBuffer, srcImage, srcInfo, true, srcLayer + layer, srcLevel + level, 0, 0, copyWidth, copyHeight);
int dstBufferWidth = sizeInBlocks ? copyWidth * blockWidth : BitUtils.DivRoundUp(copyWidth, blockWidth);
int dstBufferHeight = sizeInBlocks ? copyHeight * blockHeight : BitUtils.DivRoundUp(copyHeight, blockHeight);
CopyFromOrToBuffer(cbs, tempBuffer, dstImage, dstInfo, false, dstLayer + layer, dstLevel + level, 0, 0, dstBufferWidth, dstBufferHeight);
}
else if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples)
{
// TODO
Logger.Warning?.PrintMsg(LogClass.Gpu, "Unsupported mismatching sample count copy");
}
else
{
blitCommandEncoder.CopyFromTexture(
srcImage,
(ulong)(srcLayer + layer),
(ulong)(srcLevel + level),
new MTLOrigin { z = (ulong)srcZ },
new MTLSize { width = (ulong)copyWidth, height = (ulong)copyHeight, depth = (ulong)srcDepth },
dstImage,
(ulong)(dstLayer + layer),
(ulong)(dstLevel + level),
new MTLOrigin { z = (ulong)dstZ });
}
}
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (srcInfo.Target == Target.Texture3D)
{
srcDepth = Math.Max(1, srcDepth >> 1);
}
}
if (tempBuffer.NativePtr != 0)
{
tempBuffer.Dispose();
}
}
}
}

View File

@ -0,0 +1,60 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
readonly internal struct VertexBufferState
{
public static VertexBufferState Null => new(BufferHandle.Null, 0, 0, 0);
private readonly BufferHandle _handle;
private readonly int _offset;
private readonly int _size;
public readonly int Stride;
public readonly int Divisor;
public VertexBufferState(BufferHandle handle, int offset, int size, int divisor, int stride = 0)
{
_handle = handle;
_offset = offset;
_size = size;
Stride = stride;
Divisor = divisor;
}
public (MTLBuffer, int) GetVertexBuffer(BufferManager bufferManager, CommandBufferScoped cbs)
{
Auto<DisposableBuffer> autoBuffer = null;
if (_handle != BufferHandle.Null)
{
// TODO: Handle restride if necessary
autoBuffer = bufferManager.GetBuffer(_handle, false, out int size);
// The original stride must be reapplied in case it was rewritten.
// TODO: Handle restride if necessary
if (_offset >= size)
{
autoBuffer = null;
}
}
if (autoBuffer != null)
{
int offset = _offset;
var buffer = autoBuffer.Get(cbs, offset, _size).Value;
return (buffer, offset);
}
return (new MTLBuffer(IntPtr.Zero), 0);
}
}
}

View File

@ -0,0 +1,231 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.Effects;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Window : IWindow, IDisposable
{
public bool ScreenCaptureRequested { get; set; }
private readonly MetalRenderer _renderer;
private readonly CAMetalLayer _metalLayer;
private int _width;
private int _height;
private int _requestedWidth;
private int _requestedHeight;
// private bool _vsyncEnabled;
private AntiAliasing _currentAntiAliasing;
private bool _updateEffect;
private IPostProcessingEffect _effect;
private IScalingFilter _scalingFilter;
private bool _isLinear;
// private float _scalingFilterLevel;
private bool _updateScalingFilter;
private ScalingFilter _currentScalingFilter;
// private bool _colorSpacePassthroughEnabled;
public Window(MetalRenderer renderer, CAMetalLayer metalLayer)
{
_renderer = renderer;
_metalLayer = metalLayer;
}
private unsafe void ResizeIfNeeded()
{
if (_requestedWidth != 0 && _requestedHeight != 0)
{
// TODO: This is actually a CGSize, but there is no overload for that, so fill the first two fields of rect with the size.
var rect = new NSRect(_requestedWidth, _requestedHeight, 0, 0);
ObjectiveC.objc_msgSend(_metalLayer, "setDrawableSize:", rect);
_requestedWidth = 0;
_requestedHeight = 0;
}
}
public unsafe void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
{
if (_renderer.Pipeline is Pipeline pipeline && texture is Texture tex)
{
ResizeIfNeeded();
var drawable = new CAMetalDrawable(ObjectiveC.IntPtr_objc_msgSend(_metalLayer, "nextDrawable"));
_width = (int)drawable.Texture.Width;
_height = (int)drawable.Texture.Height;
UpdateEffect();
if (_effect != null)
{
// TODO: Run Effects
// view = _effect.Run()
}
int srcX0, srcX1, srcY0, srcY1;
if (crop.Left == 0 && crop.Right == 0)
{
srcX0 = 0;
srcX1 = tex.Width;
}
else
{
srcX0 = crop.Left;
srcX1 = crop.Right;
}
if (crop.Top == 0 && crop.Bottom == 0)
{
srcY0 = 0;
srcY1 = tex.Height;
}
else
{
srcY0 = crop.Top;
srcY1 = crop.Bottom;
}
if (ScreenCaptureRequested)
{
// TODO: Support screen captures
ScreenCaptureRequested = false;
}
float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
int dstWidth = (int)(_width * ratioX);
int dstHeight = (int)(_height * ratioY);
int dstPaddingX = (_width - dstWidth) / 2;
int dstPaddingY = (_height - dstHeight) / 2;
int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
int dstY0 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
int dstY1 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
if (_scalingFilter != null)
{
// TODO: Run scaling filter
}
pipeline.Present(
drawable,
tex,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY0, dstX1, dstY1),
_isLinear);
}
}
public void SetSize(int width, int height)
{
_requestedWidth = width;
_requestedHeight = height;
}
public void ChangeVSyncMode(VSyncMode vSyncMode)
{
//_vSyncMode = vSyncMode;
}
public void SetAntiAliasing(AntiAliasing effect)
{
if (_currentAntiAliasing == effect && _effect != null)
{
return;
}
_currentAntiAliasing = effect;
_updateEffect = true;
}
public void SetScalingFilter(ScalingFilter type)
{
if (_currentScalingFilter == type && _effect != null)
{
return;
}
_currentScalingFilter = type;
_updateScalingFilter = true;
}
public void SetScalingFilterLevel(float level)
{
// _scalingFilterLevel = level;
_updateScalingFilter = true;
}
public void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled)
{
// _colorSpacePassthroughEnabled = colorSpacePassThroughEnabled;
}
private void UpdateEffect()
{
if (_updateEffect)
{
_updateEffect = false;
switch (_currentAntiAliasing)
{
case AntiAliasing.Fxaa:
_effect?.Dispose();
Logger.Warning?.PrintMsg(LogClass.Gpu, "FXAA not implemented for Metal backend!");
break;
case AntiAliasing.None:
_effect?.Dispose();
_effect = null;
break;
case AntiAliasing.SmaaLow:
case AntiAliasing.SmaaMedium:
case AntiAliasing.SmaaHigh:
case AntiAliasing.SmaaUltra:
// var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
Logger.Warning?.PrintMsg(LogClass.Gpu, "SMAA not implemented for Metal backend!");
break;
}
}
if (_updateScalingFilter)
{
_updateScalingFilter = false;
switch (_currentScalingFilter)
{
case ScalingFilter.Bilinear:
case ScalingFilter.Nearest:
_scalingFilter?.Dispose();
_scalingFilter = null;
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
break;
case ScalingFilter.Fsr:
Logger.Warning?.PrintMsg(LogClass.Gpu, "FSR not implemented for Metal backend!");
break;
}
}
}
public void Dispose()
{
_metalLayer.Dispose();
}
}
}

View File

@ -0,0 +1,108 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System.Text;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
class CodeGenContext
{
public const string Tab = " ";
// The number of additional arguments that every function (except for the main one) must have (for instance support_buffer)
public const int AdditionalArgCount = 2;
public StructuredFunction CurrentFunction { get; set; }
public StructuredProgramInfo Info { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public OperandManager OperandManager { get; }
private readonly StringBuilder _sb;
private int _level;
private string _indentation;
public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters)
{
Info = info;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
OperandManager = new OperandManager();
_sb = new StringBuilder();
}
public void AppendLine()
{
_sb.AppendLine();
}
public void AppendLine(string str)
{
_sb.AppendLine(_indentation + str);
}
public string GetCode()
{
return _sb.ToString();
}
public void EnterScope(string prefix = "")
{
AppendLine(prefix + "{");
_level++;
UpdateIndentation();
}
public void LeaveScope(string suffix = "")
{
if (_level == 0)
{
return;
}
_level--;
UpdateIndentation();
AppendLine("}" + suffix);
}
public StructuredFunction GetFunction(int id)
{
return Info.Functions[id];
}
private void UpdateIndentation()
{
_indentation = GetIndentation(_level);
}
private static string GetIndentation(int level)
{
string indentation = string.Empty;
for (int index = 0; index < level; index++)
{
indentation += Tab;
}
return indentation;
}
}
}

View File

@ -0,0 +1,578 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class Declarations
{
/*
* Description of MSL Binding Model
*
* There are a few fundamental differences between how GLSL and MSL handle I/O.
* This comment will set out to describe the reasons why things are done certain ways
* and to describe the overall binding model that we're striving for here.
*
* Main I/O Structs
*
* Each stage has a main input and output struct (if applicable) labeled as [Stage][In/Out], i.e VertexIn.
* Every field within these structs is labeled with an [[attribute(n)]] property,
* and the overall struct is labeled with [[stage_in]] for input structs, and defined as the
* output type of the main shader function for the output struct. This struct also contains special
* attribute-based properties like [[position]] that would be "built-ins" in a GLSL context.
*
* These structs are passed as inputs to all inline functions due to containing "built-ins"
* that inline functions assume access to.
*
* Vertex & Zero Buffers
*
* Binding indices 0-16 are reserved for vertex buffers, and binding 18 is reserved for the zero buffer.
*
* Uniforms & Storage Buffers
*
* Uniforms and storage buffers are tightly packed into their respective argument buffers
* (effectively ignoring binding indices at shader level), with each pointer to the corresponding
* struct that defines the layout and fields of these buffers (usually just a single data array), laid
* out one after the other in ascending order of their binding index.
*
* The uniforms argument buffer is always bound at a fixed index of 20.
* The storage buffers argument buffer is always bound at a fixed index of 21.
*
* These structs are passed as inputs to all inline functions as in GLSL or SPIRV,
* uniforms and storage buffers would be globals, and inline functions assume access to these buffers.
*
* Samplers & Textures
*
* Metal does not have a combined image sampler like sampler2D in GLSL, as a result we need to bind
* an individual texture and a sampler object for each instance of a combined image sampler.
* Samplers and textures are bound in a shared argument buffer. This argument buffer is tightly packed
* (effectively ignoring binding indices at shader level), with texture and their samplers (if present)
* laid out one after the other in ascending order of their binding index.
*
* The samplers and textures argument buffer is always bound at a fixed index of 22.
*
*/
public static int[] Declare(CodeGenContext context, StructuredProgramInfo info)
{
// TODO: Re-enable this warning
context.AppendLine("#pragma clang diagnostic ignored \"-Wunused-variable\"");
context.AppendLine();
context.AppendLine("#include <metal_stdlib>");
context.AppendLine("#include <simd/simd.h>");
context.AppendLine();
context.AppendLine("using namespace metal;");
context.AppendLine();
var fsi = (info.HelperFunctionsMask & HelperFunctionsMask.FSI) != 0;
DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
context.AppendLine();
DeclareOutputAttributes(context, info.IoDefinitions.Where(x => x.StorageKind == StorageKind.Output));
context.AppendLine();
DeclareBufferStructures(context, context.Properties.ConstantBuffers.Values.OrderBy(x => x.Binding).ToArray(), true, fsi);
DeclareBufferStructures(context, context.Properties.StorageBuffers.Values.OrderBy(x => x.Binding).ToArray(), false, fsi);
// We need to declare each set as a new struct
var textureDefinitions = context.Properties.Textures.Values
.GroupBy(x => x.Set)
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Binding).ToArray());
var imageDefinitions = context.Properties.Images.Values
.GroupBy(x => x.Set)
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Binding).ToArray());
var textureSets = textureDefinitions.Keys.ToArray();
var imageSets = imageDefinitions.Keys.ToArray();
var sets = textureSets.Union(imageSets).ToArray();
foreach (var set in textureDefinitions)
{
DeclareTextures(context, set.Value, set.Key);
}
foreach (var set in imageDefinitions)
{
DeclareImages(context, set.Value, set.Key, fsi);
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.FindLSB) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Msl/HelperFunctions/FindLSB.metal");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.FindMSBS32) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Msl/HelperFunctions/FindMSBS32.metal");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.FindMSBU32) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Msl/HelperFunctions/FindMSBU32.metal");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Msl/HelperFunctions/SwizzleAdd.metal");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.Precise) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Msl/HelperFunctions/Precise.metal");
}
return sets;
}
static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
{
return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
}
public static void DeclareLocals(CodeGenContext context, StructuredFunction function, ShaderStage stage, bool isMainFunc = false)
{
if (isMainFunc)
{
// TODO: Support OaIndexing
if (context.Definitions.IaIndexing)
{
context.EnterScope($"array<float4, {Constants.MaxAttributes}> {Defaults.IAttributePrefix} = ");
for (int i = 0; i < Constants.MaxAttributes; i++)
{
context.AppendLine($"in.{Defaults.IAttributePrefix}{i},");
}
context.LeaveScope(";");
}
DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true);
switch (stage)
{
case ShaderStage.Vertex:
context.AppendLine("VertexOut out = {};");
// TODO: Only add if necessary
context.AppendLine("uint instance_index = instance_id + base_instance;");
break;
case ShaderStage.Fragment:
context.AppendLine("FragmentOut out = {};");
break;
}
// TODO: Only add if necessary
if (stage != ShaderStage.Compute)
{
// MSL does not give us access to [[thread_index_in_simdgroup]]
// outside compute. But we may still need to provide this value in frag/vert.
context.AppendLine("uint thread_index_in_simdgroup = simd_prefix_exclusive_sum(1);");
}
}
foreach (AstOperand decl in function.Locals)
{
string name = context.OperandManager.DeclareLocal(decl);
context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
}
}
public static string GetVarTypeName(AggregateType type, bool atomic = false)
{
var s32 = atomic ? "atomic_int" : "int";
var u32 = atomic ? "atomic_uint" : "uint";
return type switch
{
AggregateType.Void => "void",
AggregateType.Bool => "bool",
AggregateType.FP32 => "float",
AggregateType.S32 => s32,
AggregateType.U32 => u32,
AggregateType.Vector2 | AggregateType.Bool => "bool2",
AggregateType.Vector2 | AggregateType.FP32 => "float2",
AggregateType.Vector2 | AggregateType.S32 => "int2",
AggregateType.Vector2 | AggregateType.U32 => "uint2",
AggregateType.Vector3 | AggregateType.Bool => "bool3",
AggregateType.Vector3 | AggregateType.FP32 => "float3",
AggregateType.Vector3 | AggregateType.S32 => "int3",
AggregateType.Vector3 | AggregateType.U32 => "uint3",
AggregateType.Vector4 | AggregateType.Bool => "bool4",
AggregateType.Vector4 | AggregateType.FP32 => "float4",
AggregateType.Vector4 | AggregateType.S32 => "int4",
AggregateType.Vector4 | AggregateType.U32 => "uint4",
_ => throw new ArgumentException($"Invalid variable type \"{type}\"."),
};
}
private static void DeclareMemories(CodeGenContext context, IEnumerable<MemoryDefinition> memories, bool isShared)
{
string prefix = isShared ? "threadgroup " : string.Empty;
foreach (var memory in memories)
{
string arraySize = "";
if ((memory.Type & AggregateType.Array) != 0)
{
arraySize = $"[{memory.ArrayLength}]";
}
var typeName = GetVarTypeName(memory.Type & ~AggregateType.Array);
context.AppendLine($"{prefix}{typeName} {memory.Name}{arraySize};");
}
}
private static void DeclareBufferStructures(CodeGenContext context, BufferDefinition[] buffers, bool constant, bool fsi)
{
var name = constant ? "ConstantBuffers" : "StorageBuffers";
var addressSpace = constant ? "constant" : "device";
string[] bufferDec = new string[buffers.Length];
for (int i = 0; i < buffers.Length; i++)
{
BufferDefinition buffer = buffers[i];
var needsPadding = buffer.Layout == BufferLayout.Std140;
string fsiSuffix = !constant && fsi ? " [[raster_order_group(0)]]" : "";
bufferDec[i] = $"{addressSpace} {Defaults.StructPrefix}_{buffer.Name}* {buffer.Name}{fsiSuffix};";
context.AppendLine($"struct {Defaults.StructPrefix}_{buffer.Name}");
context.EnterScope();
foreach (StructureField field in buffer.Type.Fields)
{
var type = field.Type;
type |= (needsPadding && (field.Type & AggregateType.Array) != 0)
? AggregateType.Vector4
: AggregateType.Invalid;
type &= ~AggregateType.Array;
string typeName = GetVarTypeName(type);
string arraySuffix = "";
if (field.Type.HasFlag(AggregateType.Array))
{
if (field.ArrayLength > 0)
{
arraySuffix = $"[{field.ArrayLength}]";
}
else
{
// Probably UB, but this is the approach that MVK takes
arraySuffix = "[1]";
}
}
context.AppendLine($"{typeName} {field.Name}{arraySuffix};");
}
context.LeaveScope(";");
context.AppendLine();
}
context.AppendLine($"struct {name}");
context.EnterScope();
foreach (var declaration in bufferDec)
{
context.AppendLine(declaration);
}
context.LeaveScope(";");
context.AppendLine();
}
private static void DeclareTextures(CodeGenContext context, TextureDefinition[] textures, int set)
{
var setName = GetNameForSet(set);
context.AppendLine($"struct {setName}");
context.EnterScope();
List<string> textureDec = [];
foreach (TextureDefinition texture in textures)
{
if (texture.Type != SamplerType.None)
{
var textureTypeName = texture.Type.ToMslTextureType(texture.Format.GetComponentType());
if (texture.ArrayLength > 1)
{
textureTypeName = $"array<{textureTypeName}, {texture.ArrayLength}>";
}
textureDec.Add($"{textureTypeName} tex_{texture.Name};");
}
if (!texture.Separate && texture.Type != SamplerType.TextureBuffer)
{
var samplerType = "sampler";
if (texture.ArrayLength > 1)
{
samplerType = $"array<{samplerType}, {texture.ArrayLength}>";
}
textureDec.Add($"{samplerType} samp_{texture.Name};");
}
}
foreach (var declaration in textureDec)
{
context.AppendLine(declaration);
}
context.LeaveScope(";");
context.AppendLine();
}
private static void DeclareImages(CodeGenContext context, TextureDefinition[] images, int set, bool fsi)
{
var setName = GetNameForSet(set);
context.AppendLine($"struct {setName}");
context.EnterScope();
string[] imageDec = new string[images.Length];
for (int i = 0; i < images.Length; i++)
{
TextureDefinition image = images[i];
var imageTypeName = image.Type.ToMslTextureType(image.Format.GetComponentType(), true);
if (image.ArrayLength > 1)
{
imageTypeName = $"array<{imageTypeName}, {image.ArrayLength}>";
}
string fsiSuffix = fsi ? " [[raster_order_group(0)]]" : "";
imageDec[i] = $"{imageTypeName} {image.Name}{fsiSuffix};";
}
foreach (var declaration in imageDec)
{
context.AppendLine(declaration);
}
context.LeaveScope(";");
context.AppendLine();
}
private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Definitions.Stage == ShaderStage.Compute)
{
return;
}
switch (context.Definitions.Stage)
{
case ShaderStage.Vertex:
context.AppendLine("struct VertexIn");
break;
case ShaderStage.Fragment:
context.AppendLine("struct FragmentIn");
break;
}
context.EnterScope();
if (context.Definitions.Stage == ShaderStage.Fragment)
{
// TODO: check if it's needed
context.AppendLine("float4 position [[position, invariant]];");
context.AppendLine("bool front_facing [[front_facing]];");
context.AppendLine("float2 point_coord [[point_coord]];");
context.AppendLine("uint primitive_id [[primitive_id]];");
}
if (context.Definitions.IaIndexing)
{
// MSL does not support arrays in stage I/O
// We need to use the SPIRV-Cross workaround
for (int i = 0; i < Constants.MaxAttributes; i++)
{
var suffix = context.Definitions.Stage == ShaderStage.Fragment ? $"[[user(loc{i})]]" : $"[[attribute({i})]]";
context.AppendLine($"float4 {Defaults.IAttributePrefix}{i} {suffix};");
}
}
if (inputs.Any())
{
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
if (context.Definitions.IaIndexing && ioDefinition.IoVariable == IoVariable.UserDefined)
{
continue;
}
string iq = string.Empty;
if (context.Definitions.Stage == ShaderStage.Fragment)
{
iq = context.Definitions.ImapTypes[ioDefinition.Location].GetFirstUsedType() switch
{
PixelImap.Constant => "[[flat]] ",
PixelImap.ScreenLinear => "[[center_no_perspective]] ",
_ => string.Empty,
};
}
string type = ioDefinition.IoVariable switch
{
// IoVariable.Position => "float4",
IoVariable.GlobalId => "uint3",
IoVariable.VertexId => "uint",
IoVariable.VertexIndex => "uint",
// IoVariable.PointCoord => "float2",
_ => GetVarTypeName(context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput: false))
};
string name = ioDefinition.IoVariable switch
{
// IoVariable.Position => "position",
IoVariable.GlobalId => "global_id",
IoVariable.VertexId => "vertex_id",
IoVariable.VertexIndex => "vertex_index",
// IoVariable.PointCoord => "point_coord",
_ => $"{Defaults.IAttributePrefix}{ioDefinition.Location}"
};
string suffix = ioDefinition.IoVariable switch
{
// IoVariable.Position => "[[position, invariant]]",
IoVariable.GlobalId => "[[thread_position_in_grid]]",
IoVariable.VertexId => "[[vertex_id]]",
// TODO: Avoid potential redeclaration
IoVariable.VertexIndex => "[[vertex_id]]",
// IoVariable.PointCoord => "[[point_coord]]",
IoVariable.UserDefined => context.Definitions.Stage == ShaderStage.Fragment ? $"[[user(loc{ioDefinition.Location})]]" : $"[[attribute({ioDefinition.Location})]]",
_ => ""
};
context.AppendLine($"{type} {name} {iq}{suffix};");
}
}
context.LeaveScope(";");
}
private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
switch (context.Definitions.Stage)
{
case ShaderStage.Vertex:
context.AppendLine("struct VertexOut");
break;
case ShaderStage.Fragment:
context.AppendLine("struct FragmentOut");
break;
case ShaderStage.Compute:
context.AppendLine("struct KernelOut");
break;
}
context.EnterScope();
if (context.Definitions.OaIndexing)
{
// MSL does not support arrays in stage I/O
// We need to use the SPIRV-Cross workaround
for (int i = 0; i < Constants.MaxAttributes; i++)
{
context.AppendLine($"float4 {Defaults.OAttributePrefix}{i} [[user(loc{i})]];");
}
}
if (outputs.Any())
{
outputs = outputs.OrderBy(x => x.Location);
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
IoDefinition firstOutput = outputs.ElementAtOrDefault(0);
IoDefinition secondOutput = outputs.ElementAtOrDefault(1);
var type1 = GetVarTypeName(context.Definitions.GetFragmentOutputColorType(firstOutput.Location));
var type2 = GetVarTypeName(context.Definitions.GetFragmentOutputColorType(secondOutput.Location));
var name1 = $"color{firstOutput.Location}";
var name2 = $"color{firstOutput.Location + 1}";
context.AppendLine($"{type1} {name1} [[color({firstOutput.Location}), index(0)]];");
context.AppendLine($"{type2} {name2} [[color({firstOutput.Location}), index(1)]];");
outputs = outputs.Skip(2);
}
foreach (var ioDefinition in outputs)
{
if (context.Definitions.OaIndexing && ioDefinition.IoVariable == IoVariable.UserDefined)
{
continue;
}
string type = ioDefinition.IoVariable switch
{
IoVariable.Position => "float4",
IoVariable.PointSize => "float",
IoVariable.FragmentOutputColor => GetVarTypeName(context.Definitions.GetFragmentOutputColorType(ioDefinition.Location)),
IoVariable.FragmentOutputDepth => "float",
IoVariable.ClipDistance => "float",
_ => GetVarTypeName(context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput: true))
};
string name = ioDefinition.IoVariable switch
{
IoVariable.Position => "position",
IoVariable.PointSize => "point_size",
IoVariable.FragmentOutputColor => $"color{ioDefinition.Location}",
IoVariable.FragmentOutputDepth => "depth",
IoVariable.ClipDistance => "clip_distance",
_ => $"{Defaults.OAttributePrefix}{ioDefinition.Location}"
};
string suffix = ioDefinition.IoVariable switch
{
IoVariable.Position => "[[position, invariant]]",
IoVariable.PointSize => "[[point_size]]",
IoVariable.UserDefined => $"[[user(loc{ioDefinition.Location})]]",
IoVariable.FragmentOutputColor => $"[[color({ioDefinition.Location})]]",
IoVariable.FragmentOutputDepth => "[[depth(any)]]",
IoVariable.ClipDistance => $"[[clip_distance]][{Defaults.TotalClipDistances}]",
_ => ""
};
context.AppendLine($"{type} {name} {suffix};");
}
}
context.LeaveScope(";");
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);
code = code.Replace("\t", CodeGenContext.Tab);
context.AppendLine(code);
context.AppendLine();
}
public static string GetNameForSet(int set, bool forVar = false)
{
return (uint)set switch
{
Defaults.TexturesSetIndex => forVar ? "textures" : "Textures",
Defaults.ImagesSetIndex => forVar ? "images" : "Images",
_ => $"{(forVar ? "set" : "Set")}{set}"
};
}
}
}

View File

@ -0,0 +1,34 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class Defaults
{
public const string LocalNamePrefix = "temp";
public const string PerPatchAttributePrefix = "patchAttr";
public const string IAttributePrefix = "inAttr";
public const string OAttributePrefix = "outAttr";
public const string StructPrefix = "struct";
public const string ArgumentNamePrefix = "a";
public const string UndefinedName = "0";
public const int MaxVertexBuffers = 16;
public const uint ZeroBufferIndex = MaxVertexBuffers;
public const uint BaseSetIndex = MaxVertexBuffers + 1;
public const uint ConstantBuffersIndex = BaseSetIndex;
public const uint StorageBuffersIndex = BaseSetIndex + 1;
public const uint TexturesIndex = BaseSetIndex + 2;
public const uint ImagesIndex = BaseSetIndex + 3;
public const uint ConstantBuffersSetIndex = 0;
public const uint StorageBuffersSetIndex = 1;
public const uint TexturesSetIndex = 2;
public const uint ImagesSetIndex = 3;
public const int TotalClipDistances = 8;
}
}

View File

@ -0,0 +1,5 @@
template<typename T>
inline T findLSB(T x)
{
return select(ctz(x), T(-1), x == T(0));
}

View File

@ -0,0 +1,5 @@
template<typename T>
inline T findMSBS32(T x)
{
return select(clz(T(0)) - (clz(x) + T(1)), T(-1), x == T(0));
}

View File

@ -0,0 +1,6 @@
template<typename T>
inline T findMSBU32(T x)
{
T v = select(x, T(-1) - x, x < T(0));
return select(clz(T(0)) - (clz(v) + T(1)), T(-1), v == T(0));
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class HelperFunctionNames
{
public static string FindLSB = "findLSB";
public static string FindMSBS32 = "findMSBS32";
public static string FindMSBU32 = "findMSBU32";
public static string SwizzleAdd = "swizzleAdd";
}
}

View File

@ -0,0 +1,14 @@
template<typename T>
[[clang::optnone]] T PreciseFAdd(T l, T r) {
return fma(T(1), l, r);
}
template<typename T>
[[clang::optnone]] T PreciseFSub(T l, T r) {
return fma(T(-1), r, l);
}
template<typename T>
[[clang::optnone]] T PreciseFMul(T l, T r) {
return fma(l, r, T(0));
}

View File

@ -0,0 +1,7 @@
float swizzleAdd(float x, float y, int mask, uint thread_index_in_simdgroup)
{
float4 xLut = float4(1.0, -1.0, 1.0, 0.0);
float4 yLut = float4(1.0, 1.0, -1.0, 1.0);
int lutIdx = (mask >> (int(thread_index_in_simdgroup & 3u) * 2)) & 3;
return x * xLut[lutIdx] + y * yLut[lutIdx];
}

View File

@ -0,0 +1,185 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Text;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenBallot;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenBarrier;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenCall;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenMemory;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenVector;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGen
{
public static string GetExpression(CodeGenContext context, IAstNode node)
{
if (node is AstOperation operation)
{
return GetExpression(context, operation);
}
else if (node is AstOperand operand)
{
return context.OperandManager.GetExpression(context, operand);
}
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
}
private static string GetExpression(CodeGenContext context, AstOperation operation)
{
Instruction inst = operation.Inst;
InstInfo info = GetInstructionInfo(inst);
if ((info.Type & InstType.Call) != 0)
{
bool atomic = (info.Type & InstType.Atomic) != 0;
int arity = (int)(info.Type & InstType.ArityMask);
StringBuilder builder = new();
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory))
{
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
? AggregateType.S32
: AggregateType.U32;
var shared = operation.StorageKind == StorageKind.SharedMemory;
builder.Append($"({(shared ? "threadgroup" : "device")} {Declarations.GetVarTypeName(dstType, true)}*)&{GenerateLoadOrStore(context, operation, isStore: false)}");
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
{
builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}, memory_order_relaxed");
}
}
else
{
for (int argIndex = 0; argIndex < arity; argIndex++)
{
if (argIndex != 0)
{
builder.Append(", ");
}
AggregateType dstType = GetSrcVarType(inst, argIndex);
builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType));
}
if ((operation.Inst & Instruction.Mask) == Instruction.SwizzleAdd)
{
// SwizzleAdd takes one last argument, the thread_index_in_simdgroup
builder.Append(", thread_index_in_simdgroup");
}
}
return $"{info.OpName}({builder})";
}
else if ((info.Type & InstType.Op) != 0)
{
string op = info.OpName;
if (inst == Instruction.Return && operation.SourcesCount != 0)
{
return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}";
}
if (inst == Instruction.Return && context.Definitions.Stage is ShaderStage.Vertex or ShaderStage.Fragment)
{
return $"{op} out";
}
int arity = (int)(info.Type & InstType.ArityMask);
string[] expr = new string[arity];
for (int index = 0; index < arity; index++)
{
IAstNode src = operation.GetSource(index);
string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index));
bool isLhs = arity == 2 && index == 0;
expr[index] = Enclose(srcExpr, src, inst, info, isLhs);
}
switch (arity)
{
case 0:
return op;
case 1:
return op + expr[0];
case 2:
if (operation.ForcePrecise)
{
var func = (inst & Instruction.Mask) switch
{
Instruction.Add => "PreciseFAdd",
Instruction.Subtract => "PreciseFSub",
Instruction.Multiply => "PreciseFMul",
};
return $"{func}({expr[0]}, {expr[1]})";
}
return $"{expr[0]} {op} {expr[1]}";
case 3:
return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}";
}
}
else if ((info.Type & InstType.Special) != 0)
{
switch (inst & Instruction.Mask)
{
case Instruction.Ballot:
return Ballot(context, operation);
case Instruction.Call:
return Call(context, operation);
case Instruction.FSIBegin:
case Instruction.FSIEnd:
return "// FSI implemented with raster order groups in MSL";
case Instruction.GroupMemoryBarrier:
case Instruction.MemoryBarrier:
case Instruction.Barrier:
return Barrier(context, operation);
case Instruction.ImageLoad:
case Instruction.ImageStore:
case Instruction.ImageAtomic:
return ImageLoadOrStore(context, operation);
case Instruction.Load:
return Load(context, operation);
case Instruction.Lod:
return Lod(context, operation);
case Instruction.Store:
return Store(context, operation);
case Instruction.TextureSample:
return TextureSample(context, operation);
case Instruction.TextureQuerySamples:
return TextureQuerySamples(context, operation);
case Instruction.TextureQuerySize:
return TextureQuerySize(context, operation);
case Instruction.PackHalf2x16:
return PackHalf2x16(context, operation);
case Instruction.UnpackHalf2x16:
return UnpackHalf2x16(context, operation);
case Instruction.VectorExtract:
return VectorExtract(context, operation);
case Instruction.VoteAllEqual:
return VoteAllEqual(context, operation);
}
}
// TODO: Return this to being an error
return $"Unexpected instruction type \"{info.Type}\".";
}
}
}

View File

@ -0,0 +1,30 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenBallot
{
public static string Ballot(CodeGenContext context, AstOperation operation)
{
AggregateType dstType = GetSrcVarType(operation.Inst, 0);
string arg = GetSourceExpr(context, operation.GetSource(0), dstType);
char component = "xyzw"[operation.Index];
return $"uint4(as_type<uint2>((simd_vote::vote_t)simd_ballot({arg})), 0, 0).{component}";
}
public static string VoteAllEqual(CodeGenContext context, AstOperation operation)
{
AggregateType dstType = GetSrcVarType(operation.Inst, 0);
string arg = GetSourceExpr(context, operation.GetSource(0), dstType);
return $"simd_all({arg}) || !simd_any({arg})";
}
}
}

View File

@ -0,0 +1,15 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenBarrier
{
public static string Barrier(CodeGenContext context, AstOperation operation)
{
var device = (operation.Inst & Instruction.Mask) == Instruction.MemoryBarrier;
return $"threadgroup_barrier(mem_flags::mem_{(device ? "device" : "threadgroup")})";
}
}
}

View File

@ -0,0 +1,60 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenCall
{
public static string Call(CodeGenContext context, AstOperation operation)
{
AstOperand funcId = (AstOperand)operation.GetSource(0);
var function = context.GetFunction(funcId.Value);
int argCount = operation.SourcesCount - 1;
int additionalArgCount = CodeGenContext.AdditionalArgCount + (context.Definitions.Stage != ShaderStage.Compute ? 1 : 0);
bool needsThreadIndex = false;
// TODO: Replace this with a proper flag
if (function.Name.Contains("Shuffle"))
{
needsThreadIndex = true;
additionalArgCount++;
}
string[] args = new string[argCount + additionalArgCount];
// Additional arguments
if (context.Definitions.Stage != ShaderStage.Compute)
{
args[0] = "in";
args[1] = "constant_buffers";
args[2] = "storage_buffers";
if (needsThreadIndex)
{
args[3] = "thread_index_in_simdgroup";
}
}
else
{
args[0] = "constant_buffers";
args[1] = "storage_buffers";
if (needsThreadIndex)
{
args[2] = "thread_index_in_simdgroup";
}
}
int argIndex = additionalArgCount;
for (int i = 0; i < argCount; i++)
{
args[argIndex++] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i));
}
return $"{function.Name}({string.Join(", ", args)})";
}
}
}

View File

@ -0,0 +1,222 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.TypeConversion;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenHelper
{
private static readonly InstInfo[] _infoTable;
static InstGenHelper()
{
_infoTable = new InstInfo[(int)Instruction.Count];
#pragma warning disable IDE0055 // Disable formatting
Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomic_fetch_add_explicit");
Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomic_fetch_and_explicit");
Add(Instruction.AtomicCompareAndSwap, InstType.AtomicBinary, "atomic_compare_exchange_weak_explicit");
Add(Instruction.AtomicMaxU32, InstType.AtomicBinary, "atomic_fetch_max_explicit");
Add(Instruction.AtomicMinU32, InstType.AtomicBinary, "atomic_fetch_min_explicit");
Add(Instruction.AtomicOr, InstType.AtomicBinary, "atomic_fetch_or_explicit");
Add(Instruction.AtomicSwap, InstType.AtomicBinary, "atomic_exchange_explicit");
Add(Instruction.AtomicXor, InstType.AtomicBinary, "atomic_fetch_xor_explicit");
Add(Instruction.Absolute, InstType.CallUnary, "abs");
Add(Instruction.Add, InstType.OpBinaryCom, "+", 2);
Add(Instruction.Ballot, InstType.Special);
Add(Instruction.Barrier, InstType.Special);
Add(Instruction.BitCount, InstType.CallUnary, "popcount");
Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "extract_bits");
Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "extract_bits");
Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "insert_bits");
Add(Instruction.BitfieldReverse, InstType.CallUnary, "reverse_bits");
Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6);
Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7);
Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0);
Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8);
Add(Instruction.Call, InstType.Special);
Add(Instruction.Ceiling, InstType.CallUnary, "ceil");
Add(Instruction.Clamp, InstType.CallTernary, "clamp");
Add(Instruction.ClampU32, InstType.CallTernary, "clamp");
Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5);
Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4);
Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4);
Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4);
Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4);
Add(Instruction.CompareLess, InstType.OpBinary, "<", 4);
Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4);
Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4);
Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4);
Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5);
Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12);
Add(Instruction.ConvertFP32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP64ToFP32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP32ToS32, InstType.CallUnary, "int");
Add(Instruction.ConvertFP32ToU32, InstType.CallUnary, "uint");
Add(Instruction.ConvertFP64ToS32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP64ToU32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertS32ToFP32, InstType.CallUnary, "float");
Add(Instruction.ConvertS32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertU32ToFP32, InstType.CallUnary, "float");
Add(Instruction.ConvertU32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.Cosine, InstType.CallUnary, "cos");
Add(Instruction.Ddx, InstType.CallUnary, "dfdx");
Add(Instruction.Ddy, InstType.CallUnary, "dfdy");
Add(Instruction.Discard, InstType.CallNullary, "discard_fragment");
Add(Instruction.Divide, InstType.OpBinary, "/", 1);
Add(Instruction.EmitVertex, 0); // MSL does not have geometry shaders
Add(Instruction.EndPrimitive, 0); // MSL does not have geometry shaders
Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
Add(Instruction.FSIBegin, InstType.Special);
Add(Instruction.FSIEnd, InstType.Special);
Add(Instruction.FindLSB, InstType.CallUnary, HelperFunctionNames.FindLSB);
Add(Instruction.FindMSBS32, InstType.CallUnary, HelperFunctionNames.FindMSBS32);
Add(Instruction.FindMSBU32, InstType.CallUnary, HelperFunctionNames.FindMSBU32);
Add(Instruction.Floor, InstType.CallUnary, "floor");
Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
Add(Instruction.GroupMemoryBarrier, InstType.Special);
Add(Instruction.ImageLoad, InstType.Special);
Add(Instruction.ImageStore, InstType.Special);
Add(Instruction.ImageAtomic, InstType.Special); // Metal 3.1+
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.Load, InstType.Special);
Add(Instruction.Lod, InstType.Special);
Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^", 10);
Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0);
Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11);
Add(Instruction.LoopBreak, InstType.OpNullary, "break");
Add(Instruction.LoopContinue, InstType.OpNullary, "continue");
Add(Instruction.PackDouble2x32, 0); // MSL does not have a 64-bit FP
Add(Instruction.PackHalf2x16, InstType.Special);
Add(Instruction.Maximum, InstType.CallBinary, "max");
Add(Instruction.MaximumU32, InstType.CallBinary, "max");
Add(Instruction.MemoryBarrier, InstType.Special);
Add(Instruction.Minimum, InstType.CallBinary, "min");
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
Add(Instruction.Modulo, InstType.CallBinary, "fmod");
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
Add(Instruction.MultiplyHighS32, InstType.CallBinary, "mulhi");
Add(Instruction.MultiplyHighU32, InstType.CallBinary, "mulhi");
Add(Instruction.Negate, InstType.OpUnary, "-");
Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "rsqrt");
Add(Instruction.Return, InstType.OpNullary, "return");
Add(Instruction.Round, InstType.CallUnary, "round");
Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3);
Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3);
Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3);
Add(Instruction.Shuffle, InstType.CallBinary, "simd_shuffle");
Add(Instruction.ShuffleDown, InstType.CallBinary, "simd_shuffle_down");
Add(Instruction.ShuffleUp, InstType.CallBinary, "simd_shuffle_up");
Add(Instruction.ShuffleXor, InstType.CallBinary, "simd_shuffle_xor");
Add(Instruction.Sine, InstType.CallUnary, "sin");
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
Add(Instruction.Store, InstType.Special);
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
Add(Instruction.TextureSample, InstType.Special);
Add(Instruction.TextureQuerySamples, InstType.Special);
Add(Instruction.TextureQuerySize, InstType.Special);
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
Add(Instruction.UnpackDouble2x32, 0); // MSL does not have a 64-bit FP
Add(Instruction.UnpackHalf2x16, InstType.Special);
Add(Instruction.VectorExtract, InstType.Special);
Add(Instruction.VoteAll, InstType.CallUnary, "simd_all");
Add(Instruction.VoteAllEqual, InstType.Special);
Add(Instruction.VoteAny, InstType.CallUnary, "simd_any");
#pragma warning restore IDE0055
}
private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0)
{
_infoTable[(int)inst] = new InstInfo(flags, opName, precedence);
}
public static InstInfo GetInstructionInfo(Instruction inst)
{
return _infoTable[(int)(inst & Instruction.Mask)];
}
public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType)
{
return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType);
}
public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs)
{
InstInfo pInfo = GetInstructionInfo(pInst);
return Enclose(expr, node, pInst, pInfo, isLhs);
}
public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false)
{
if (NeedsParenthesis(node, pInst, pInfo, isLhs))
{
expr = "(" + expr + ")";
}
return expr;
}
public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs)
{
// If the node isn't an operation, then it can only be an operand,
// and those never needs to be surrounded in parentheses.
if (node is not AstOperation operation)
{
// This is sort of a special case, if this is a negative constant,
// and it is consumed by a unary operation, we need to put on the parenthesis,
// as in MSL, while a sequence like ~-1 is valid, --2 is not.
if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary)
{
return true;
}
return false;
}
if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0)
{
return false;
}
InstInfo info = _infoTable[(int)(operation.Inst & Instruction.Mask)];
if ((info.Type & (InstType.Call | InstType.Special)) != 0)
{
return false;
}
if (info.Precedence < pInfo.Precedence)
{
return false;
}
if (info.Precedence == pInfo.Precedence && isLhs)
{
return false;
}
if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom)
{
return false;
}
return true;
}
private static bool IsNegativeConst(IAstNode node)
{
if (node is not AstOperand operand)
{
return false;
}
return operand.Type == OperandType.Constant && operand.Value < 0;
}
}
}

View File

@ -0,0 +1,672 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Text;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenMemory
{
public static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
string varName;
AggregateType varType;
int srcIndex = 0;
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
int inputsCount = isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount;
bool fieldHasPadding = false;
if (operation.Inst == Instruction.AtomicCompareAndSwap)
{
inputsCount--;
}
string fieldName = "";
switch (storageKind)
{
case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (operation.GetSource(srcIndex++) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[binding]
: context.Properties.StorageBuffers[binding];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
StructureField field = buffer.Type.Fields[fieldIndex.Value];
fieldHasPadding = buffer.Layout == BufferLayout.Std140
&& ((field.Type & AggregateType.Vector4) == 0)
&& ((field.Type & AggregateType.Array) != 0);
varName = storageKind == StorageKind.ConstantBuffer
? "constant_buffers"
: "storage_buffers";
varName += "." + buffer.Name;
varName += "->" + field.Name;
varType = field.Type;
break;
case StorageKind.LocalMemory:
case StorageKind.SharedMemory:
if (operation.GetSource(srcIndex++) is not AstOperand { Type: OperandType.Constant } bindingId)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
MemoryDefinition memory = storageKind == StorageKind.LocalMemory
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
varName = memory.Name;
varType = memory.Type;
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (operation.GetSource(srcIndex++) is not AstOperand varId || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = -1;
int component = 0;
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, vecIndex.Value, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetMslBuiltIn(
context.Definitions,
ioVariable,
location,
component,
isOutput,
isPerPatch);
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else
{
varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]";
}
}
varName += fieldName;
varName += fieldHasPadding ? ".x" : "";
if (isStore)
{
varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}";
}
return varName;
}
public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isArray = (texOp.Type & SamplerType.Array) != 0;
var texCallBuilder = new StringBuilder();
int srcIndex = 0;
string Src(AggregateType type)
{
return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
}
string imageName = GetImageName(context, texOp, ref srcIndex);
texCallBuilder.Append(imageName);
texCallBuilder.Append('.');
if (texOp.Inst == Instruction.ImageAtomic)
{
texCallBuilder.Append((texOp.Flags & TextureFlags.AtomicMask) switch
{
TextureFlags.Add => "atomic_fetch_add",
TextureFlags.Minimum => "atomic_min",
TextureFlags.Maximum => "atomic_max",
TextureFlags.Increment => "atomic_fetch_add",
TextureFlags.Decrement => "atomic_fetch_sub",
TextureFlags.BitwiseAnd => "atomic_fetch_and",
TextureFlags.BitwiseOr => "atomic_fetch_or",
TextureFlags.BitwiseXor => "atomic_fetch_xor",
TextureFlags.Swap => "atomic_exchange",
TextureFlags.CAS => "atomic_compare_exchange_weak",
_ => "atomic_fetch_add",
});
}
else
{
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "read" : "write");
}
texCallBuilder.Append('(');
var coordsBuilder = new StringBuilder();
int coordsCount = texOp.Type.GetDimensions();
if (coordsCount > 1)
{
string[] elems = new string[coordsCount];
for (int index = 0; index < coordsCount; index++)
{
elems[index] = Src(AggregateType.S32);
}
coordsBuilder.Append($"uint{coordsCount}({string.Join(", ", elems)})");
}
else
{
coordsBuilder.Append($"uint({Src(AggregateType.S32)})");
}
if (isArray)
{
coordsBuilder.Append(", ");
coordsBuilder.Append(Src(AggregateType.S32));
}
if (texOp.Inst == Instruction.ImageStore)
{
AggregateType type = texOp.Format.GetComponentType();
string[] cElems = new string[4];
for (int index = 0; index < 4; index++)
{
if (srcIndex < texOp.SourcesCount)
{
cElems[index] = Src(type);
}
else
{
cElems[index] = type switch
{
AggregateType.S32 => NumberFormatter.FormatInt(0),
AggregateType.U32 => NumberFormatter.FormatUint(0),
_ => NumberFormatter.FormatFloat(0),
};
}
}
string prefix = type switch
{
AggregateType.S32 => "int",
AggregateType.U32 => "uint",
AggregateType.FP32 => "float",
_ => string.Empty,
};
texCallBuilder.Append($"{prefix}4({string.Join(", ", cElems)})");
texCallBuilder.Append(", ");
}
texCallBuilder.Append(coordsBuilder);
if (texOp.Inst == Instruction.ImageAtomic)
{
texCallBuilder.Append(", ");
AggregateType type = texOp.Format.GetComponentType();
if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS)
{
texCallBuilder.Append(Src(type)); // Compare value.
}
string value = (texOp.Flags & TextureFlags.AtomicMask) switch
{
TextureFlags.Increment => NumberFormatter.FormatInt(1, type), // TODO: Clamp value
TextureFlags.Decrement => NumberFormatter.FormatInt(-1, type), // TODO: Clamp value
_ => Src(type),
};
texCallBuilder.Append(value);
// This doesn't match what the MSL spec document says so either
// it is wrong or the MSL compiler has a bug.
texCallBuilder.Append(")[0]");
}
else
{
texCallBuilder.Append(')');
if (texOp.Inst == Instruction.ImageLoad)
{
texCallBuilder.Append(GetMaskMultiDest(texOp.Index));
}
}
return texCallBuilder.ToString();
}
public static string Load(CodeGenContext context, AstOperation operation)
{
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string Lod(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = 0;
string textureName = GetTextureName(context, texOp, ref coordsIndex);
string samplerName = GetSamplerName(context, texOp, ref coordsIndex);
string coordsExpr;
if (coordsCount > 1)
{
string[] elems = new string[coordsCount];
for (int index = 0; index < coordsCount; index++)
{
elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32);
}
coordsExpr = "float" + coordsCount + "(" + string.Join(", ", elems) + ")";
}
else
{
coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32);
}
var clamped = $"{textureName}.calculate_clamped_lod({samplerName}, {coordsExpr})";
var unclamped = $"{textureName}.calculate_unclamped_lod({samplerName}, {coordsExpr})";
return $"float2({clamped}, {unclamped}){GetMask(texOp.Index)}";
}
public static string Store(CodeGenContext context, AstOperation operation)
{
return GenerateLoadOrStore(context, operation, isStore: true);
}
public static string TextureSample(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0;
bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
var texCallBuilder = new StringBuilder();
bool colorIsVector = isGather || !isShadow;
int srcIndex = 0;
string Src(AggregateType type)
{
return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
}
string textureName = GetTextureName(context, texOp, ref srcIndex);
string samplerName = GetSamplerName(context, texOp, ref srcIndex);
texCallBuilder.Append(textureName);
texCallBuilder.Append('.');
if (intCoords)
{
texCallBuilder.Append("read(");
}
else
{
if (isGather)
{
texCallBuilder.Append("gather");
}
else
{
texCallBuilder.Append("sample");
}
if (isShadow)
{
texCallBuilder.Append("_compare");
}
texCallBuilder.Append($"({samplerName}, ");
}
int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount;
bool appended = false;
void Append(string str)
{
if (appended)
{
texCallBuilder.Append(", ");
}
else
{
appended = true;
}
texCallBuilder.Append(str);
}
AggregateType coordType = intCoords ? AggregateType.S32 : AggregateType.FP32;
string AssemblePVector(int count)
{
string coords;
if (count > 1)
{
string[] elems = new string[count];
for (int index = 0; index < count; index++)
{
elems[index] = Src(coordType);
}
coords = string.Join(", ", elems);
}
else
{
coords = Src(coordType);
}
string prefix = intCoords ? "uint" : "float";
return prefix + (count > 1 ? count : "") + "(" + coords + ")";
}
Append(AssemblePVector(pCount));
if (isArray)
{
Append(Src(AggregateType.S32));
}
if (isShadow)
{
Append(Src(AggregateType.FP32));
}
if (hasDerivatives)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Unused sampler derivatives!");
}
if (hasLodBias)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Unused sample LOD bias!");
}
if (hasLodLevel)
{
if (intCoords)
{
Append(Src(coordType));
}
else
{
Append($"level({Src(coordType)})");
}
}
string AssembleOffsetVector(int count)
{
if (count > 1)
{
string[] elems = new string[count];
for (int index = 0; index < count; index++)
{
elems[index] = Src(AggregateType.S32);
}
return "int" + count + "(" + string.Join(", ", elems) + ")";
}
else
{
return Src(AggregateType.S32);
}
}
// TODO: Support reads with offsets
if (!intCoords)
{
if (hasOffset)
{
Append(AssembleOffsetVector(coordsCount));
}
else if (hasOffsets)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, "Multiple offsets on gathers are not yet supported!");
}
}
texCallBuilder.Append(')');
texCallBuilder.Append(colorIsVector ? GetMaskMultiDest(texOp.Index) : "");
return texCallBuilder.ToString();
}
private static string GetTextureName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
TextureDefinition textureDefinition = context.Properties.Textures[texOp.GetTextureSetAndBinding()];
string name = textureDefinition.Name;
string setName = Declarations.GetNameForSet(textureDefinition.Set, true);
if (textureDefinition.ArrayLength != 1)
{
name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
return $"{setName}.tex_{name}";
}
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
var index = texOp.IsSeparate ? texOp.GetSamplerSetAndBinding() : texOp.GetTextureSetAndBinding();
var sourceIndex = texOp.IsSeparate ? srcIndex++ : srcIndex + 1;
TextureDefinition samplerDefinition = context.Properties.Textures[index];
string name = samplerDefinition.Name;
string setName = Declarations.GetNameForSet(samplerDefinition.Set, true);
if (samplerDefinition.ArrayLength != 1)
{
name = $"{name}[{GetSourceExpr(context, texOp.GetSource(sourceIndex), AggregateType.S32)}]";
}
return $"{setName}.samp_{name}";
}
private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
TextureDefinition imageDefinition = context.Properties.Images[texOp.GetTextureSetAndBinding()];
string name = imageDefinition.Name;
string setName = Declarations.GetNameForSet(imageDefinition.Set, true);
if (imageDefinition.ArrayLength != 1)
{
name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
return $"{setName}.{name}";
}
private static string GetMaskMultiDest(int mask)
{
if (mask == 0x0)
{
return "";
}
string swizzle = ".";
for (int i = 0; i < 4; i++)
{
if ((mask & (1 << i)) != 0)
{
swizzle += "xyzw"[i];
}
}
return swizzle;
}
public static string TextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
string textureName = GetTextureName(context, texOp, ref srcIndex);
return $"{textureName}.get_num_samples()";
}
public static string TextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
var texCallBuilder = new StringBuilder();
int srcIndex = 0;
string textureName = GetTextureName(context, texOp, ref srcIndex);
texCallBuilder.Append(textureName);
texCallBuilder.Append('.');
if (texOp.Index == 3)
{
texCallBuilder.Append("get_num_mip_levels()");
}
else
{
context.Properties.Textures.TryGetValue(texOp.GetTextureSetAndBinding(), out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
bool isArray = definition.Type.HasFlag(SamplerType.Array);
texCallBuilder.Append("get_");
if (texOp.Index == 0)
{
texCallBuilder.Append("width");
}
else if (texOp.Index == 1)
{
texCallBuilder.Append("height");
}
else
{
if (isArray)
{
texCallBuilder.Append("array_size");
}
else
{
texCallBuilder.Append("depth");
}
}
texCallBuilder.Append('(');
if (hasLod && !isArray)
{
IAstNode lod = operation.GetSource(0);
string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, 0));
texCallBuilder.Append(lodExpr);
}
texCallBuilder.Append(')');
}
return texCallBuilder.ToString();
}
public static string PackHalf2x16(CodeGenContext context, AstOperation operation)
{
IAstNode src0 = operation.GetSource(0);
IAstNode src1 = operation.GetSource(1);
string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0));
string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1));
return $"as_type<uint>(half2({src0Expr}, {src1Expr}))";
}
public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation)
{
IAstNode src = operation.GetSource(0);
string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0));
return $"float2(as_type<half2>({srcExpr})){GetMask(operation.Index)}";
}
private static string GetMask(int index)
{
return $".{"xy".AsSpan(index, 1)}";
}
}
}

View File

@ -0,0 +1,32 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenVector
{
public static string VectorExtract(CodeGenContext context, AstOperation operation)
{
IAstNode vector = operation.GetSource(0);
IAstNode index = operation.GetSource(1);
string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector));
if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant)
{
char elem = "xyzw"[indexOperand.Value];
return $"{vectorExpr}.{elem}";
}
else
{
string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1));
return $"{vectorExpr}[{indexExpr}]";
}
}
}
}

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
readonly struct InstInfo
{
public InstType Type { get; }
public string OpName { get; }
public int Precedence { get; }
public InstInfo(InstType type, string opName, int precedence)
{
Type = type;
OpName = opName;
Precedence = precedence;
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
[Flags]
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
public enum InstType
{
OpNullary = Op | 0,
OpUnary = Op | 1,
OpBinary = Op | 2,
OpBinaryCom = Op | 2 | Commutative,
OpTernary = Op | 3,
CallNullary = Call | 0,
CallUnary = Call | 1,
CallBinary = Call | 2,
CallTernary = Call | 3,
CallQuaternary = Call | 4,
// The atomic instructions have one extra operand,
// for the storage slot and offset pair.
AtomicBinary = Call | Atomic | 3,
AtomicTernary = Call | Atomic | 4,
Commutative = 1 << 8,
Op = 1 << 9,
Call = 1 << 10,
Atomic = 1 << 11,
Special = 1 << 12,
ArityMask = 0xff,
}
}

View File

@ -0,0 +1,83 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class IoMap
{
public static (string, AggregateType) GetMslBuiltIn(
ShaderDefinitions definitions,
IoVariable ioVariable,
int location,
int component,
bool isOutput,
bool isPerPatch)
{
var returnValue = ioVariable switch
{
IoVariable.BaseInstance => ("base_instance", AggregateType.U32),
IoVariable.BaseVertex => ("base_vertex", AggregateType.U32),
IoVariable.CtaId => ("threadgroup_position_in_grid", AggregateType.Vector3 | AggregateType.U32),
IoVariable.ClipDistance => ("out.clip_distance", AggregateType.Array | AggregateType.FP32),
IoVariable.FragmentOutputColor => ($"out.color{location}", definitions.GetFragmentOutputColorType(location)),
IoVariable.FragmentOutputDepth => ("out.depth", AggregateType.FP32),
IoVariable.FrontFacing => ("in.front_facing", AggregateType.Bool),
IoVariable.GlobalId => ("thread_position_in_grid", AggregateType.Vector3 | AggregateType.U32),
IoVariable.InstanceId => ("instance_id", AggregateType.U32),
IoVariable.InstanceIndex => ("instance_index", AggregateType.U32),
IoVariable.InvocationId => ("INVOCATION_ID", AggregateType.S32),
IoVariable.PointCoord => ("in.point_coord", AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => ("out.point_size", AggregateType.FP32),
IoVariable.Position => ("out.position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => ("in.primitive_id", AggregateType.U32),
IoVariable.SubgroupEqMask => ("thread_index_in_simdgroup >= 32 ? uint4(0, (1 << (thread_index_in_simdgroup - 32)), uint2(0)) : uint4(1 << thread_index_in_simdgroup, uint3(0))", AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGeMask => ("uint4(insert_bits(0u, 0xFFFFFFFF, thread_index_in_simdgroup, 32 - thread_index_in_simdgroup), uint3(0)) & (uint4((uint)((simd_vote::vote_t)simd_ballot(true) & 0xFFFFFFFF), (uint)(((simd_vote::vote_t)simd_ballot(true) >> 32) & 0xFFFFFFFF), 0, 0))", AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGtMask => ("uint4(insert_bits(0u, 0xFFFFFFFF, thread_index_in_simdgroup + 1, 32 - thread_index_in_simdgroup - 1), uint3(0)) & (uint4((uint)((simd_vote::vote_t)simd_ballot(true) & 0xFFFFFFFF), (uint)(((simd_vote::vote_t)simd_ballot(true) >> 32) & 0xFFFFFFFF), 0, 0))", AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLaneId => ("thread_index_in_simdgroup", AggregateType.U32),
IoVariable.SubgroupLeMask => ("uint4(extract_bits(0xFFFFFFFF, 0, min(thread_index_in_simdgroup + 1, 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)thread_index_in_simdgroup + 1 - 32, 0)), uint2(0))", AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLtMask => ("uint4(extract_bits(0xFFFFFFFF, 0, min(thread_index_in_simdgroup, 32u)), extract_bits(0xFFFFFFFF, 0, (uint)max((int)thread_index_in_simdgroup - 32, 0)), uint2(0))", AggregateType.Vector4 | AggregateType.U32),
IoVariable.ThreadKill => ("simd_is_helper_thread()", AggregateType.Bool),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch),
IoVariable.ThreadId => ("thread_position_in_threadgroup", AggregateType.Vector3 | AggregateType.U32),
IoVariable.VertexId => ("vertex_id", AggregateType.S32),
// gl_VertexIndex does not have a direct equivalent in MSL
IoVariable.VertexIndex => ("vertex_id", AggregateType.U32),
IoVariable.ViewportIndex => ("viewport_array_index", AggregateType.S32),
IoVariable.FragmentCoord => ("in.position", AggregateType.Vector4 | AggregateType.FP32),
_ => (null, AggregateType.Invalid),
};
if (returnValue.Item2 == AggregateType.Invalid)
{
Logger.Warning?.PrintMsg(LogClass.Gpu, $"Unable to find type for IoVariable {ioVariable}!");
}
return returnValue;
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? Defaults.PerPatchAttributePrefix
: (isOutput ? Defaults.OAttributePrefix : Defaults.IAttributePrefix);
if (location < 0)
{
return (name, definitions.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
string prefix = isOutput ? "out" : "in";
return (prefix + "." + name, definitions.GetUserDefinedType(location, isOutput));
}
}
}

View File

@ -0,0 +1,286 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Linq;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.TypeConversion;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class MslGenerator
{
public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
if (parameters.Definitions.Stage is not (ShaderStage.Vertex or ShaderStage.Fragment or ShaderStage.Compute))
{
Logger.Warning?.Print(LogClass.Gpu, $"Attempted to generate unsupported shader type {parameters.Definitions.Stage}!");
return "";
}
CodeGenContext context = new(info, parameters);
var sets = Declarations.Declare(context, info);
if (info.Functions.Count != 0)
{
for (int i = 1; i < info.Functions.Count; i++)
{
PrintFunction(context, info.Functions[i], parameters.Definitions.Stage, sets);
context.AppendLine();
}
}
PrintFunction(context, info.Functions[0], parameters.Definitions.Stage, sets, true);
return context.GetCode();
}
private static void PrintFunction(CodeGenContext context, StructuredFunction function, ShaderStage stage, int[] sets, bool isMainFunc = false)
{
context.CurrentFunction = function;
context.AppendLine(GetFunctionSignature(context, function, stage, sets, isMainFunc));
context.EnterScope();
Declarations.DeclareLocals(context, function, stage, isMainFunc);
PrintBlock(context, function.MainBlock, isMainFunc);
// In case the shader hasn't returned, return
if (isMainFunc && stage != ShaderStage.Compute)
{
context.AppendLine("return out;");
}
context.LeaveScope();
}
private static string GetFunctionSignature(
CodeGenContext context,
StructuredFunction function,
ShaderStage stage,
int[] sets,
bool isMainFunc = false)
{
int additionalArgCount = isMainFunc ? 0 : CodeGenContext.AdditionalArgCount + (context.Definitions.Stage != ShaderStage.Compute ? 1 : 0);
bool needsThreadIndex = false;
// TODO: Replace this with a proper flag
if (function.Name.Contains("Shuffle"))
{
needsThreadIndex = true;
additionalArgCount++;
}
string[] args = new string[additionalArgCount + function.InArguments.Length + function.OutArguments.Length];
// All non-main functions need to be able to access the support_buffer as well
if (!isMainFunc)
{
if (stage != ShaderStage.Compute)
{
args[0] = stage == ShaderStage.Vertex ? "VertexIn in" : "FragmentIn in";
args[1] = "constant ConstantBuffers &constant_buffers";
args[2] = "device StorageBuffers &storage_buffers";
if (needsThreadIndex)
{
args[3] = "uint thread_index_in_simdgroup";
}
}
else
{
args[0] = "constant ConstantBuffers &constant_buffers";
args[1] = "device StorageBuffers &storage_buffers";
if (needsThreadIndex)
{
args[2] = "uint thread_index_in_simdgroup";
}
}
}
int argIndex = additionalArgCount;
for (int i = 0; i < function.InArguments.Length; i++)
{
args[argIndex++] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
}
for (int i = 0; i < function.OutArguments.Length; i++)
{
int j = i + function.InArguments.Length;
args[argIndex++] = $"thread {Declarations.GetVarTypeName(function.OutArguments[i])} &{OperandManager.GetArgumentName(j)}";
}
string funcKeyword = "inline";
string funcName = null;
string returnType = Declarations.GetVarTypeName(function.ReturnType);
if (isMainFunc)
{
if (stage == ShaderStage.Vertex)
{
funcKeyword = "vertex";
funcName = "vertexMain";
returnType = "VertexOut";
}
else if (stage == ShaderStage.Fragment)
{
funcKeyword = "fragment";
funcName = "fragmentMain";
returnType = "FragmentOut";
}
else if (stage == ShaderStage.Compute)
{
funcKeyword = "kernel";
funcName = "kernelMain";
returnType = "void";
}
if (stage == ShaderStage.Vertex)
{
args = args.Prepend("VertexIn in [[stage_in]]").ToArray();
}
else if (stage == ShaderStage.Fragment)
{
args = args.Prepend("FragmentIn in [[stage_in]]").ToArray();
}
// TODO: add these only if they are used
if (stage == ShaderStage.Vertex)
{
args = args.Append("uint vertex_id [[vertex_id]]").ToArray();
args = args.Append("uint instance_id [[instance_id]]").ToArray();
args = args.Append("uint base_instance [[base_instance]]").ToArray();
args = args.Append("uint base_vertex [[base_vertex]]").ToArray();
}
else if (stage == ShaderStage.Compute)
{
args = args.Append("uint3 threadgroup_position_in_grid [[threadgroup_position_in_grid]]").ToArray();
args = args.Append("uint3 thread_position_in_grid [[thread_position_in_grid]]").ToArray();
args = args.Append("uint3 thread_position_in_threadgroup [[thread_position_in_threadgroup]]").ToArray();
args = args.Append("uint thread_index_in_simdgroup [[thread_index_in_simdgroup]]").ToArray();
}
args = args.Append($"constant ConstantBuffers &constant_buffers [[buffer({Defaults.ConstantBuffersIndex})]]").ToArray();
args = args.Append($"device StorageBuffers &storage_buffers [[buffer({Defaults.StorageBuffersIndex})]]").ToArray();
foreach (var set in sets)
{
var bindingIndex = set + Defaults.BaseSetIndex;
args = args.Append($"constant {Declarations.GetNameForSet(set)} &{Declarations.GetNameForSet(set, true)} [[buffer({bindingIndex})]]").ToArray();
}
}
var funcPrefix = $"{funcKeyword} {returnType} {funcName ?? function.Name}(";
var indent = new string(' ', funcPrefix.Length);
return $"{funcPrefix}{string.Join($", \n{indent}", args)})";
}
private static void PrintBlock(CodeGenContext context, AstBlock block, bool isMainFunction)
{
AstBlockVisitor visitor = new(block);
visitor.BlockEntered += (sender, e) =>
{
switch (e.Block.Type)
{
case AstBlockType.DoWhile:
context.AppendLine("do");
break;
case AstBlockType.Else:
context.AppendLine("else");
break;
case AstBlockType.ElseIf:
context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})");
break;
case AstBlockType.If:
context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})");
break;
default:
throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\".");
}
context.EnterScope();
};
visitor.BlockLeft += (sender, e) =>
{
context.LeaveScope();
if (e.Block.Type == AstBlockType.DoWhile)
{
context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});");
}
};
bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence;
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
{
if (node is AstOperation operation)
{
if (!supportsBarrierDivergence)
{
if (operation.Inst == IntermediateRepresentation.Instruction.Barrier)
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Logger.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}
}
else if (operation.Inst == IntermediateRepresentation.Instruction.Return)
{
mayHaveReturned = true;
}
}
string expr = InstGen.GetExpression(context, operation);
if (expr != null)
{
context.AppendLine(expr + ";");
}
}
else if (node is AstAssignment assignment)
{
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination);
AggregateType srcType = OperandManager.GetNodeDestType(context, assignment.Source);
string dest = InstGen.GetExpression(context, assignment.Destination);
string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
context.AppendLine(dest + " = " + src + ";");
}
else if (node is AstComment comment)
{
context.AppendLine("// " + comment.Comment);
}
else
{
throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\".");
}
}
}
private static string GetCondExpr(CodeGenContext context, IAstNode cond)
{
AggregateType srcType = OperandManager.GetNodeDestType(context, cond);
return ReinterpretCast(context, cond, srcType, AggregateType.Bool);
}
}
}

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