mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-06-04 19:55:19 +00:00
Added curved window mode on visionOS 26 (#15298)
This commit is contained in:
parent
f30ec9940a
commit
5cf16e4522
22 changed files with 2328 additions and 99 deletions
|
|
@ -50,13 +50,14 @@
|
||||||
0000AEB9AE90228CA2D60000 /* SDL_asyncio.c in Sources */ = {isa = PBXBuildFile; fileRef = 00003928A612EC33D42C0000 /* SDL_asyncio.c */; };
|
0000AEB9AE90228CA2D60000 /* SDL_asyncio.c in Sources */ = {isa = PBXBuildFile; fileRef = 00003928A612EC33D42C0000 /* SDL_asyncio.c */; };
|
||||||
0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000CCA310B73A7B59910000 /* SDL_cocoapen.m */; };
|
0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000CCA310B73A7B59910000 /* SDL_cocoapen.m */; };
|
||||||
007317A40858DECD00B2BC32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179D0858DECD00B2BC32 /* Cocoa.framework */; platformFilters = (macos, ); };
|
007317A40858DECD00B2BC32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179D0858DECD00B2BC32 /* Cocoa.framework */; platformFilters = (macos, ); };
|
||||||
007317A60858DECD00B2BC32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179F0858DECD00B2BC32 /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, ); };
|
007317A60858DECD00B2BC32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179F0858DECD00B2BC32 /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, xros, ); };
|
||||||
00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); };
|
00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); };
|
||||||
00D0D08410675DD9004B05EF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; };
|
00D0D08410675DD9004B05EF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); settings = {ATTRIBUTES = (Required, ); }; };
|
||||||
00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; platformFilters = (macos, ); };
|
00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; platformFilters = (macos, ); };
|
||||||
02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */; };
|
02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */; };
|
||||||
1485C3312BBA4AF30063985B /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; };
|
1485C3312BBA4AF30063985B /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; };
|
3AFD09EA2F9766BA00208BA9 /* SDL_CurvedUIShader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFD09E92F9766BA00208BA9 /* SDL_CurvedUIShader.swift */; platformFilters = (xros, ); };
|
||||||
|
557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||||
5616CA4C252BB2A6005D5928 /* SDL_url.c in Sources */ = {isa = PBXBuildFile; fileRef = 5616CA49252BB2A5005D5928 /* SDL_url.c */; };
|
5616CA4C252BB2A6005D5928 /* SDL_url.c in Sources */ = {isa = PBXBuildFile; fileRef = 5616CA49252BB2A5005D5928 /* SDL_url.c */; };
|
||||||
5616CA4D252BB2A6005D5928 /* SDL_sysurl.h in Headers */ = {isa = PBXBuildFile; fileRef = 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */; };
|
5616CA4D252BB2A6005D5928 /* SDL_sysurl.h in Headers */ = {isa = PBXBuildFile; fileRef = 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */; };
|
||||||
|
|
@ -82,8 +83,8 @@
|
||||||
A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; };
|
A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; };
|
||||||
A1BB8B6327F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; };
|
A1BB8B6327F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; };
|
||||||
A1BB8B6C27F6CF330057CFA8 /* SDL_list.h in Headers */ = {isa = PBXBuildFile; fileRef = A1BB8B6227F6CF330057CFA8 /* SDL_list.h */; };
|
A1BB8B6C27F6CF330057CFA8 /* SDL_list.h in Headers */ = {isa = PBXBuildFile; fileRef = A1BB8B6227F6CF330057CFA8 /* SDL_list.h */; };
|
||||||
A7381E961D8B69D600B177DD /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; };
|
A7381E961D8B69D600B177DD /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); settings = {ATTRIBUTES = (Required, ); }; };
|
||||||
A7381E971D8B6A0300B177DD /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E931D8B69C300B177DD /* AudioToolbox.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); };
|
A7381E971D8B6A0300B177DD /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E931D8B69C300B177DD /* AudioToolbox.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); };
|
||||||
A75FDB5823E39E6100529352 /* hidapi.h in Headers */ = {isa = PBXBuildFile; fileRef = A75FDB5723E39E6100529352 /* hidapi.h */; };
|
A75FDB5823E39E6100529352 /* hidapi.h in Headers */ = {isa = PBXBuildFile; fileRef = A75FDB5723E39E6100529352 /* hidapi.h */; };
|
||||||
A75FDBC523EA380300529352 /* SDL_hidapi_rumble.h in Headers */ = {isa = PBXBuildFile; fileRef = A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */; };
|
A75FDBC523EA380300529352 /* SDL_hidapi_rumble.h in Headers */ = {isa = PBXBuildFile; fileRef = A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */; };
|
||||||
A75FDBCE23EA380300529352 /* SDL_hidapi_rumble.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */; };
|
A75FDBCE23EA380300529352 /* SDL_hidapi_rumble.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */; };
|
||||||
|
|
@ -368,8 +369,6 @@
|
||||||
E4F257972C81903800FCEAFC /* SDL_sysgpu.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F257862C81903800FCEAFC /* SDL_sysgpu.h */; };
|
E4F257972C81903800FCEAFC /* SDL_sysgpu.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F257862C81903800FCEAFC /* SDL_sysgpu.h */; };
|
||||||
E4F257982C81903800FCEAFC /* SDL_gpu_openxr.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F257882C81903800FCEAFC /* SDL_gpu_openxr.c */; };
|
E4F257982C81903800FCEAFC /* SDL_gpu_openxr.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F257882C81903800FCEAFC /* SDL_gpu_openxr.c */; };
|
||||||
E4F257992C81903800FCEAFC /* SDL_openxrdyn.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F257892C81903800FCEAFC /* SDL_openxrdyn.c */; };
|
E4F257992C81903800FCEAFC /* SDL_openxrdyn.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F257892C81903800FCEAFC /* SDL_openxrdyn.c */; };
|
||||||
E4F2579A2C81903800FCEAFC /* SDL_gpu_openxr_c.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F2578A2C81903800FCEAFC /* SDL_gpu_openxr_c.h */; };
|
|
||||||
E4F2579B2C81903800FCEAFC /* SDL_openxr_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F2578C2C81903800FCEAFC /* SDL_openxr_internal.h */; };
|
|
||||||
E4F7981A2AD8D84800669F54 /* SDL_core_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F798192AD8D84800669F54 /* SDL_core_unsupported.c */; };
|
E4F7981A2AD8D84800669F54 /* SDL_core_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F798192AD8D84800669F54 /* SDL_core_unsupported.c */; };
|
||||||
E4F7981C2AD8D85500669F54 /* SDL_dynapi_unsupported.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F7981B2AD8D85500669F54 /* SDL_dynapi_unsupported.h */; };
|
E4F7981C2AD8D85500669F54 /* SDL_dynapi_unsupported.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F7981B2AD8D85500669F54 /* SDL_dynapi_unsupported.h */; };
|
||||||
E4F7981E2AD8D86A00669F54 /* SDL_render_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */; };
|
E4F7981E2AD8D86A00669F54 /* SDL_render_unsupported.c in Sources */ = {isa = PBXBuildFile; fileRef = E4F7981D2AD8D86A00669F54 /* SDL_render_unsupported.c */; };
|
||||||
|
|
@ -434,6 +433,13 @@
|
||||||
F3990E062A788303000D8759 /* SDL_hidapi_ios.h in Headers */ = {isa = PBXBuildFile; fileRef = F3990E032A788303000D8759 /* SDL_hidapi_ios.h */; };
|
F3990E062A788303000D8759 /* SDL_hidapi_ios.h in Headers */ = {isa = PBXBuildFile; fileRef = F3990E032A788303000D8759 /* SDL_hidapi_ios.h */; };
|
||||||
F3990E072A78833C000D8759 /* hid.m in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAA523E2792500529352 /* hid.m */; };
|
F3990E072A78833C000D8759 /* hid.m in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAA523E2792500529352 /* hid.m */; };
|
||||||
F3A4909E2554D38600E92A8B /* SDL_hidapi_ps5.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A4909D2554D38500E92A8B /* SDL_hidapi_ps5.c */; };
|
F3A4909E2554D38600E92A8B /* SDL_hidapi_ps5.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A4909D2554D38500E92A8B /* SDL_hidapi_ps5.c */; };
|
||||||
|
F3A8371C2F69C80100AD32B6 /* SDL_RealityKitHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A837162F69C80100AD32B6 /* SDL_RealityKitHelper.swift */; platformFilters = (xros, ); };
|
||||||
|
F3A895712F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A8956D2F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift */; platformFilters = (xros, ); };
|
||||||
|
F3A895722F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A8956E2F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift */; platformFilters = (xros, ); };
|
||||||
|
F3A895792F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h in Headers */ = {isa = PBXBuildFile; fileRef = F3A895772F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h */; platformFilters = (xros, ); };
|
||||||
|
F3A8957A2F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h in Headers */ = {isa = PBXBuildFile; fileRef = F3A895782F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h */; platformFilters = (xros, ); };
|
||||||
|
F3A8957B2F7DC14400B9E5C2 /* SDL_UIKitBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = F3A895762F7DC14400B9E5C2 /* SDL_UIKitBridge.m */; platformFilters = (xros, ); };
|
||||||
|
F3A8957D2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A8957C2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift */; platformFilters = (xros, ); };
|
||||||
F3A9AE982C8A13C100AAC390 /* SDL_gpu_util.h in Headers */ = {isa = PBXBuildFile; fileRef = F3A9AE922C8A13C100AAC390 /* SDL_gpu_util.h */; };
|
F3A9AE982C8A13C100AAC390 /* SDL_gpu_util.h in Headers */ = {isa = PBXBuildFile; fileRef = F3A9AE922C8A13C100AAC390 /* SDL_gpu_util.h */; };
|
||||||
F3A9AE992C8A13C100AAC390 /* SDL_render_gpu.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A9AE932C8A13C100AAC390 /* SDL_render_gpu.c */; };
|
F3A9AE992C8A13C100AAC390 /* SDL_render_gpu.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A9AE932C8A13C100AAC390 /* SDL_render_gpu.c */; };
|
||||||
F3A9AE9A2C8A13C100AAC390 /* SDL_shaders_gpu.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A9AE942C8A13C100AAC390 /* SDL_shaders_gpu.c */; };
|
F3A9AE9A2C8A13C100AAC390 /* SDL_shaders_gpu.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A9AE942C8A13C100AAC390 /* SDL_shaders_gpu.c */; };
|
||||||
|
|
@ -559,7 +565,7 @@
|
||||||
F3FBB10A2DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FBB1092DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c */; };
|
F3FBB10A2DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FBB1092DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c */; };
|
||||||
F3FD042E2C9B755700824C4C /* SDL_hidapi_nintendo.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */; };
|
F3FD042E2C9B755700824C4C /* SDL_hidapi_nintendo.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */; };
|
||||||
F3FD042F2C9B755700824C4C /* SDL_hidapi_steam_hori.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */; };
|
F3FD042F2C9B755700824C4C /* SDL_hidapi_steam_hori.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */; };
|
||||||
FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; };
|
FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); settings = {ATTRIBUTES = (Required, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -617,6 +623,7 @@
|
||||||
00D0D08310675DD9004B05EF /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
00D0D08310675DD9004B05EF /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||||
02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_sinput.c; sourceTree = "<group>"; };
|
02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_sinput.c; sourceTree = "<group>"; };
|
||||||
1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
|
1485C32F2BBA4A0C0063985B /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
|
||||||
|
3AFD09E92F9766BA00208BA9 /* SDL_CurvedUIShader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDL_CurvedUIShader.swift; sourceTree = "<group>"; };
|
||||||
5616CA49252BB2A5005D5928 /* SDL_url.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_url.c; sourceTree = "<group>"; };
|
5616CA49252BB2A5005D5928 /* SDL_url.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_url.c; sourceTree = "<group>"; };
|
||||||
5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysurl.h; sourceTree = "<group>"; };
|
5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysurl.h; sourceTree = "<group>"; };
|
||||||
5616CA4B252BB2A6005D5928 /* SDL_sysurl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysurl.m; sourceTree = "<group>"; };
|
5616CA4B252BB2A6005D5928 /* SDL_sysurl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysurl.m; sourceTree = "<group>"; };
|
||||||
|
|
@ -970,7 +977,6 @@
|
||||||
F338A1192D1B37E4007CDFDF /* SDL_tray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray.c; sourceTree = "<group>"; };
|
F338A1192D1B37E4007CDFDF /* SDL_tray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray.c; sourceTree = "<group>"; };
|
||||||
F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_8bitdo.c; sourceTree = "<group>"; };
|
F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_8bitdo.c; sourceTree = "<group>"; };
|
||||||
F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_flydigi.c; sourceTree = "<group>"; };
|
F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_flydigi.c; sourceTree = "<group>"; };
|
||||||
F3FBB1092DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamesir.c; sourceTree = "<group>"; };
|
|
||||||
F344003C2D4022E1003F26D7 /* INSTALL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = "<group>"; };
|
F344003C2D4022E1003F26D7 /* INSTALL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = "<group>"; };
|
||||||
F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = "<group>"; };
|
F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = "<group>"; };
|
||||||
F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = "<group>"; };
|
F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = "<group>"; };
|
||||||
|
|
@ -1026,6 +1032,13 @@
|
||||||
F3990E022A788303000D8759 /* SDL_hidapi_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_mac.h; sourceTree = "<group>"; };
|
F3990E022A788303000D8759 /* SDL_hidapi_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_mac.h; sourceTree = "<group>"; };
|
||||||
F3990E032A788303000D8759 /* SDL_hidapi_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_ios.h; sourceTree = "<group>"; };
|
F3990E032A788303000D8759 /* SDL_hidapi_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_ios.h; sourceTree = "<group>"; };
|
||||||
F3A4909D2554D38500E92A8B /* SDL_hidapi_ps5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps5.c; sourceTree = "<group>"; };
|
F3A4909D2554D38500E92A8B /* SDL_hidapi_ps5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps5.c; sourceTree = "<group>"; };
|
||||||
|
F3A837162F69C80100AD32B6 /* SDL_RealityKitHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDL_RealityKitHelper.swift; sourceTree = "<group>"; };
|
||||||
|
F3A8956D2F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDL_CurvedContentHosting.swift; sourceTree = "<group>"; };
|
||||||
|
F3A8956E2F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDL_CurvedContentView.swift; sourceTree = "<group>"; };
|
||||||
|
F3A895762F7DC14400B9E5C2 /* SDL_UIKitBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDL_UIKitBridge.m; sourceTree = "<group>"; };
|
||||||
|
F3A895772F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SDL_UIKitBridge-objc.h"; sourceTree = "<group>"; };
|
||||||
|
F3A895782F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SDL_UIKitBridge-swift.h"; sourceTree = "<group>"; };
|
||||||
|
F3A8957C2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDL_uikitviewcontroller.swift; sourceTree = "<group>"; };
|
||||||
F3A9AE922C8A13C100AAC390 /* SDL_gpu_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gpu_util.h; sourceTree = "<group>"; };
|
F3A9AE922C8A13C100AAC390 /* SDL_gpu_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gpu_util.h; sourceTree = "<group>"; };
|
||||||
F3A9AE932C8A13C100AAC390 /* SDL_render_gpu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_render_gpu.c; sourceTree = "<group>"; };
|
F3A9AE932C8A13C100AAC390 /* SDL_render_gpu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_render_gpu.c; sourceTree = "<group>"; };
|
||||||
F3A9AE942C8A13C100AAC390 /* SDL_shaders_gpu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_shaders_gpu.c; sourceTree = "<group>"; };
|
F3A9AE942C8A13C100AAC390 /* SDL_shaders_gpu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_shaders_gpu.c; sourceTree = "<group>"; };
|
||||||
|
|
@ -1149,6 +1162,7 @@
|
||||||
F3FA5A1A2B59ACE000FEAD97 /* yuv_rgb_lsx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = yuv_rgb_lsx.c; sourceTree = "<group>"; };
|
F3FA5A1A2B59ACE000FEAD97 /* yuv_rgb_lsx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = yuv_rgb_lsx.c; sourceTree = "<group>"; };
|
||||||
F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = yuv_rgb_lsx.h; sourceTree = "<group>"; };
|
F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = yuv_rgb_lsx.h; sourceTree = "<group>"; };
|
||||||
F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = yuv_rgb_common.h; sourceTree = "<group>"; };
|
F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = yuv_rgb_common.h; sourceTree = "<group>"; };
|
||||||
|
F3FBB1092DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamesir.c; sourceTree = "<group>"; };
|
||||||
F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_nintendo.h; sourceTree = "<group>"; };
|
F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_nintendo.h; sourceTree = "<group>"; };
|
||||||
F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_hori.c; sourceTree = "<group>"; };
|
F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_hori.c; sourceTree = "<group>"; };
|
||||||
F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
|
F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
|
||||||
|
|
@ -1738,8 +1752,15 @@
|
||||||
A7D8A61823E2513D00DCD162 /* uikit */ = {
|
A7D8A61823E2513D00DCD162 /* uikit */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F3A8956D2F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift */,
|
||||||
|
F3A8956E2F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift */,
|
||||||
|
3AFD09E92F9766BA00208BA9 /* SDL_CurvedUIShader.swift */,
|
||||||
|
F3A837162F69C80100AD32B6 /* SDL_RealityKitHelper.swift */,
|
||||||
A7D8A62F23E2513D00DCD162 /* SDL_uikitappdelegate.h */,
|
A7D8A62F23E2513D00DCD162 /* SDL_uikitappdelegate.h */,
|
||||||
A7D8A61E23E2513D00DCD162 /* SDL_uikitappdelegate.m */,
|
A7D8A61E23E2513D00DCD162 /* SDL_uikitappdelegate.m */,
|
||||||
|
F3A895762F7DC14400B9E5C2 /* SDL_UIKitBridge.m */,
|
||||||
|
F3A895772F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h */,
|
||||||
|
F3A895782F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h */,
|
||||||
A7D8A62123E2513D00DCD162 /* SDL_uikitclipboard.h */,
|
A7D8A62123E2513D00DCD162 /* SDL_uikitclipboard.h */,
|
||||||
A7D8A62A23E2513D00DCD162 /* SDL_uikitclipboard.m */,
|
A7D8A62A23E2513D00DCD162 /* SDL_uikitclipboard.m */,
|
||||||
A7D8A62D23E2513D00DCD162 /* SDL_uikitevents.h */,
|
A7D8A62D23E2513D00DCD162 /* SDL_uikitevents.h */,
|
||||||
|
|
@ -1754,18 +1775,19 @@
|
||||||
A7D8A62323E2513D00DCD162 /* SDL_uikitopengles.m */,
|
A7D8A62323E2513D00DCD162 /* SDL_uikitopengles.m */,
|
||||||
A7D8A62B23E2513D00DCD162 /* SDL_uikitopenglview.h */,
|
A7D8A62B23E2513D00DCD162 /* SDL_uikitopenglview.h */,
|
||||||
A7D8A62023E2513D00DCD162 /* SDL_uikitopenglview.m */,
|
A7D8A62023E2513D00DCD162 /* SDL_uikitopenglview.m */,
|
||||||
|
000063D3D80F97ADC7770000 /* SDL_uikitpen.h */,
|
||||||
|
000053D344416737F6050000 /* SDL_uikitpen.m */,
|
||||||
A7D8A62223E2513D00DCD162 /* SDL_uikitvideo.h */,
|
A7D8A62223E2513D00DCD162 /* SDL_uikitvideo.h */,
|
||||||
A7D8A63223E2513D00DCD162 /* SDL_uikitvideo.m */,
|
A7D8A63223E2513D00DCD162 /* SDL_uikitvideo.m */,
|
||||||
A7D8A61923E2513D00DCD162 /* SDL_uikitview.h */,
|
A7D8A61923E2513D00DCD162 /* SDL_uikitview.h */,
|
||||||
A7D8A62923E2513D00DCD162 /* SDL_uikitview.m */,
|
A7D8A62923E2513D00DCD162 /* SDL_uikitview.m */,
|
||||||
A7D8A62423E2513D00DCD162 /* SDL_uikitviewcontroller.h */,
|
A7D8A62423E2513D00DCD162 /* SDL_uikitviewcontroller.h */,
|
||||||
A7D8A63023E2513D00DCD162 /* SDL_uikitviewcontroller.m */,
|
A7D8A63023E2513D00DCD162 /* SDL_uikitviewcontroller.m */,
|
||||||
|
F3A8957C2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift */,
|
||||||
A7D8A63323E2513D00DCD162 /* SDL_uikitvulkan.h */,
|
A7D8A63323E2513D00DCD162 /* SDL_uikitvulkan.h */,
|
||||||
A7D8A62523E2513D00DCD162 /* SDL_uikitvulkan.m */,
|
A7D8A62523E2513D00DCD162 /* SDL_uikitvulkan.m */,
|
||||||
A7D8A62723E2513D00DCD162 /* SDL_uikitwindow.h */,
|
A7D8A62723E2513D00DCD162 /* SDL_uikitwindow.h */,
|
||||||
A7D8A61A23E2513D00DCD162 /* SDL_uikitwindow.m */,
|
A7D8A61A23E2513D00DCD162 /* SDL_uikitwindow.m */,
|
||||||
000063D3D80F97ADC7770000 /* SDL_uikitpen.h */,
|
|
||||||
000053D344416737F6050000 /* SDL_uikitpen.m */,
|
|
||||||
);
|
);
|
||||||
path = uikit;
|
path = uikit;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -2365,17 +2387,6 @@
|
||||||
path = vulkan;
|
path = vulkan;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E4F2578B2C81903800FCEAFC /* xr */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E4F257882C81903800FCEAFC /* SDL_gpu_openxr.c */,
|
|
||||||
E4F257892C81903800FCEAFC /* SDL_openxrdyn.c */,
|
|
||||||
E4F2578A2C81903800FCEAFC /* SDL_gpu_openxr_c.h */,
|
|
||||||
E4F2578C2C81903800FCEAFC /* SDL_openxr_internal.h */,
|
|
||||||
);
|
|
||||||
path = xr;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E4F257872C81903800FCEAFC /* gpu */ = {
|
E4F257872C81903800FCEAFC /* gpu */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -2388,6 +2399,17 @@
|
||||||
path = gpu;
|
path = gpu;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E4F2578B2C81903800FCEAFC /* xr */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E4F257882C81903800FCEAFC /* SDL_gpu_openxr.c */,
|
||||||
|
E4F257892C81903800FCEAFC /* SDL_openxrdyn.c */,
|
||||||
|
E4F2578A2C81903800FCEAFC /* SDL_gpu_openxr_c.h */,
|
||||||
|
E4F2578C2C81903800FCEAFC /* SDL_openxr_internal.h */,
|
||||||
|
);
|
||||||
|
path = xr;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F338A1142D1B3735007CDFDF /* tray */ = {
|
F338A1142D1B3735007CDFDF /* tray */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -2561,6 +2583,8 @@
|
||||||
F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */,
|
F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */,
|
||||||
F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */,
|
F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */,
|
||||||
F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */,
|
F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */,
|
||||||
|
F3A895792F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h in Headers */,
|
||||||
|
F3A8957A2F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h in Headers */,
|
||||||
A7D8AE8E23E2514100DCD162 /* SDL_cocoakeyboard.h in Headers */,
|
A7D8AE8E23E2514100DCD162 /* SDL_cocoakeyboard.h in Headers */,
|
||||||
A7D8AF0623E2514100DCD162 /* SDL_cocoamessagebox.h in Headers */,
|
A7D8AF0623E2514100DCD162 /* SDL_cocoamessagebox.h in Headers */,
|
||||||
A7D8AEB223E2514100DCD162 /* SDL_cocoametalview.h in Headers */,
|
A7D8AEB223E2514100DCD162 /* SDL_cocoametalview.h in Headers */,
|
||||||
|
|
@ -2843,6 +2867,9 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1130;
|
LastUpgradeCheck = 1130;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
BECDF5FE0761BA81005FE872 = {
|
||||||
|
LastSwiftMigration = 2630;
|
||||||
|
};
|
||||||
F3676F582A7885080091160D = {
|
F3676F582A7885080091160D = {
|
||||||
CreatedOnToolsVersion = 14.3.1;
|
CreatedOnToolsVersion = 14.3.1;
|
||||||
};
|
};
|
||||||
|
|
@ -2931,6 +2958,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A7D8B9E323E2514400DCD162 /* SDL_drawline.c in Sources */,
|
A7D8B9E323E2514400DCD162 /* SDL_drawline.c in Sources */,
|
||||||
|
F3A8957B2F7DC14400B9E5C2 /* SDL_UIKitBridge.m in Sources */,
|
||||||
A7D8AE7C23E2514100DCD162 /* SDL_yuv.c in Sources */,
|
A7D8AE7C23E2514100DCD162 /* SDL_yuv.c in Sources */,
|
||||||
A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */,
|
A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */,
|
||||||
A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */,
|
A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */,
|
||||||
|
|
@ -3059,6 +3087,7 @@
|
||||||
F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */,
|
F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */,
|
||||||
A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */,
|
A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */,
|
||||||
A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */,
|
A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */,
|
||||||
|
F3A8957D2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift in Sources */,
|
||||||
A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */,
|
A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */,
|
||||||
89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */,
|
89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */,
|
||||||
A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */,
|
A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */,
|
||||||
|
|
@ -3086,6 +3115,7 @@
|
||||||
A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */,
|
A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */,
|
||||||
A7D8B4DC23E2514300DCD162 /* SDL_joystick.c in Sources */,
|
A7D8B4DC23E2514300DCD162 /* SDL_joystick.c in Sources */,
|
||||||
A7D8BA4923E2514400DCD162 /* SDL_render_gles2.c in Sources */,
|
A7D8BA4923E2514400DCD162 /* SDL_render_gles2.c in Sources */,
|
||||||
|
F3A8371C2F69C80100AD32B6 /* SDL_RealityKitHelper.swift in Sources */,
|
||||||
A7D8AC2D23E2514100DCD162 /* SDL_surface.c in Sources */,
|
A7D8AC2D23E2514100DCD162 /* SDL_surface.c in Sources */,
|
||||||
A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */,
|
A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */,
|
||||||
A7D8AD2323E2514100DCD162 /* SDL_blit_auto.c in Sources */,
|
A7D8AD2323E2514100DCD162 /* SDL_blit_auto.c in Sources */,
|
||||||
|
|
@ -3141,6 +3171,8 @@
|
||||||
A7D8A94B23E2514000DCD162 /* SDL.c in Sources */,
|
A7D8A94B23E2514000DCD162 /* SDL.c in Sources */,
|
||||||
A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m in Sources */,
|
A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m in Sources */,
|
||||||
A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */,
|
A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */,
|
||||||
|
F3A895712F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift in Sources */,
|
||||||
|
F3A895722F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift in Sources */,
|
||||||
566E26D8246274CC00718109 /* SDL_locale.c in Sources */,
|
566E26D8246274CC00718109 /* SDL_locale.c in Sources */,
|
||||||
63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */,
|
63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */,
|
||||||
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
|
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
|
||||||
|
|
@ -3153,6 +3185,7 @@
|
||||||
00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
|
00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
|
||||||
000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */,
|
000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */,
|
||||||
F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */,
|
F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */,
|
||||||
|
3AFD09EA2F9766BA00208BA9 /* SDL_CurvedUIShader.swift in Sources */,
|
||||||
F3FBB10A2DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c in Sources */,
|
F3FBB10A2DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c in Sources */,
|
||||||
0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */,
|
0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */,
|
||||||
0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */,
|
0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */,
|
||||||
|
|
@ -3239,13 +3272,18 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_LINK_OBJC_RUNTIME = NO;
|
CLANG_LINK_OBJC_RUNTIME = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||||
OTHER_LDFLAGS = "-liconv";
|
OTHER_LDFLAGS = "-liconv";
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
"SWIFT_OBJC_BRIDGING_HEADER[sdk=xr*]" = "../../src/video/uikit/SDL_UIKitBridge-swift.h";
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -3305,13 +3343,19 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_LINK_OBJC_RUNTIME = NO;
|
CLANG_LINK_OBJC_RUNTIME = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||||
OTHER_LDFLAGS = "-liconv";
|
OTHER_LDFLAGS = "-liconv";
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
"SWIFT_OBJC_BRIDGING_HEADER[sdk=xr*]" = "../../src/video/uikit/SDL_UIKitBridge-swift.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -163,8 +163,9 @@ typedef enum SDL_EventType
|
||||||
associated with the window. Otherwise, the handle has already been destroyed and all resources
|
associated with the window. Otherwise, the handle has already been destroyed and all resources
|
||||||
associated with it are invalid */
|
associated with it are invalid */
|
||||||
SDL_EVENT_WINDOW_HDR_STATE_CHANGED, /**< Window HDR properties have changed */
|
SDL_EVENT_WINDOW_HDR_STATE_CHANGED, /**< Window HDR properties have changed */
|
||||||
|
SDL_EVENT_WINDOW_CURVATURE_CHANGED, /**< Window curvature has changed to data1 (on visionOS) */
|
||||||
SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN,
|
SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN,
|
||||||
SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_HDR_STATE_CHANGED,
|
SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_CURVATURE_CHANGED,
|
||||||
|
|
||||||
/* Keyboard events */
|
/* Keyboard events */
|
||||||
SDL_EVENT_KEY_DOWN = 0x300, /**< Key pressed */
|
SDL_EVENT_KEY_DOWN = 0x300, /**< Key pressed */
|
||||||
|
|
|
||||||
|
|
@ -1384,6 +1384,11 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren
|
||||||
* popup windows and have the behaviors and guidelines outlined in
|
* popup windows and have the behaviors and guidelines outlined in
|
||||||
* SDL_CreatePopupWindow().
|
* SDL_CreatePopupWindow().
|
||||||
*
|
*
|
||||||
|
* These are additional supported properties with visionOS:
|
||||||
|
*
|
||||||
|
* - `SDL_PROP_WINDOW_CREATE_CURVATURE_FLOAT`: the curvature of the window on visionOS. Curved windows have square corners and additional controls for more immersive gaming.
|
||||||
|
* This can be -1 (disabled), which is the default, 0 (no curve), or set to a specific curvature radius in millimeters. A common value for a gaming monitor is 1000.
|
||||||
|
*
|
||||||
* If this window is being created to be used with an SDL_Renderer, you should
|
* If this window is being created to be used with an SDL_Renderer, you should
|
||||||
* not add a graphics API specific property
|
* not add a graphics API specific property
|
||||||
* (`SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN`, etc), as SDL will handle that
|
* (`SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN`, etc), as SDL will handle that
|
||||||
|
|
@ -1446,6 +1451,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop
|
||||||
#define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER "SDL.window.create.x11.window"
|
#define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER "SDL.window.create.x11.window"
|
||||||
#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.create.emscripten.canvas_id"
|
#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.create.emscripten.canvas_id"
|
||||||
#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.create.emscripten.keyboard_element"
|
#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.create.emscripten.keyboard_element"
|
||||||
|
#define SDL_PROP_WINDOW_CREATE_CURVATURE_FLOAT "SDL.window.create.curvature"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the numeric ID of a window.
|
* Get the numeric ID of a window.
|
||||||
|
|
@ -1624,6 +1630,10 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
|
||||||
* - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard
|
* - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard
|
||||||
* element that associates keyboard events to this window
|
* element that associates keyboard events to this window
|
||||||
*
|
*
|
||||||
|
* On visionOS:
|
||||||
|
*
|
||||||
|
* - `SDL_PROP_WINDOW_CURVATURE_FLOAT`: the curvature of the window in curved mode on visionOS. This value is updated dynamically when changed via the screen ornaments. This can be 0 (no curve), or a specific curvature radius in millimeters. A common value for a gaming monitor is 1000.
|
||||||
|
*
|
||||||
* \param window the window to query.
|
* \param window the window to query.
|
||||||
* \returns a valid property ID on success or 0 on failure; call
|
* \returns a valid property ID on success or 0 on failure; call
|
||||||
* SDL_GetError() for more information.
|
* SDL_GetError() for more information.
|
||||||
|
|
@ -1673,6 +1683,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
|
||||||
#define SDL_PROP_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window"
|
#define SDL_PROP_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window"
|
||||||
#define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.emscripten.canvas_id"
|
#define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.emscripten.canvas_id"
|
||||||
#define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.emscripten.keyboard_element"
|
#define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.emscripten.keyboard_element"
|
||||||
|
#define SDL_PROP_WINDOW_CURVATURE_FLOAT "SDL.window.curvature"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the window flags.
|
* Get the window flags.
|
||||||
|
|
|
||||||
|
|
@ -565,6 +565,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen)
|
||||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN);
|
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN);
|
||||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED);
|
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED);
|
||||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED);
|
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED);
|
||||||
|
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_CURVATURE_CHANGED);
|
||||||
#undef SDL_WINDOWEVENT_CASE
|
#undef SDL_WINDOWEVENT_CASE
|
||||||
|
|
||||||
#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->kdevice.timestamp, (uint)event->kdevice.which)
|
#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%u)", event->kdevice.timestamp, (uint)event->kdevice.which)
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,12 @@ bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat forma
|
||||||
|
|
||||||
void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props)
|
void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props)
|
||||||
{
|
{
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
// The RealityKit texture always renders in linear colorspace
|
||||||
|
renderer->output_colorspace = SDL_COLORSPACE_SRGB_LINEAR;
|
||||||
|
#else
|
||||||
renderer->output_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
|
renderer->output_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDL_RenderingLinearSpace(SDL_Renderer *renderer)
|
bool SDL_RenderingLinearSpace(SDL_Renderer *renderer)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@
|
||||||
#endif
|
#endif
|
||||||
#ifdef SDL_VIDEO_DRIVER_UIKIT
|
#ifdef SDL_VIDEO_DRIVER_UIKIT
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
#import "../../video/uikit/SDL_UIKitBridge-objc.h"
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Regenerate these with build-metal-shaders.sh
|
// Regenerate these with build-metal-shaders.sh
|
||||||
|
|
@ -139,6 +142,9 @@ typedef struct METAL_ShaderPipelines
|
||||||
@property(nonatomic, assign) METAL_ShaderPipelines *activepipelines;
|
@property(nonatomic, assign) METAL_ShaderPipelines *activepipelines;
|
||||||
@property(nonatomic, assign) METAL_ShaderPipelines *allpipelines;
|
@property(nonatomic, assign) METAL_ShaderPipelines *allpipelines;
|
||||||
@property(nonatomic, assign) int pipelinescount;
|
@property(nonatomic, assign) int pipelinescount;
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
@property(nonatomic, retain) id<MTLTexture> mtlrealitykittexture;
|
||||||
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SDL3METAL_RenderData
|
@implementation SDL3METAL_RenderData
|
||||||
|
|
@ -453,16 +459,25 @@ static bool METAL_ActivateRenderCommandEncoder(SDL_Renderer *renderer, MTLLoadAc
|
||||||
SDL3METAL_TextureData *texdata = (__bridge SDL3METAL_TextureData *)renderer->target->internal;
|
SDL3METAL_TextureData *texdata = (__bridge SDL3METAL_TextureData *)renderer->target->internal;
|
||||||
mtltexture = texdata.mtltexture;
|
mtltexture = texdata.mtltexture;
|
||||||
} else {
|
} else {
|
||||||
if (data.mtlbackbuffer == nil) {
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
/* The backbuffer's contents aren't guaranteed to persist after
|
if (renderer->window && SDL_UIKit_IsCurvedWindow(renderer->window)) {
|
||||||
* presenting, so we can leave it undefined when loading it. */
|
data.mtlrealitykittexture = SDL_UIKit_GetCurvedDisplayTexture(renderer->window, [data.mtlcmdqueue commandBuffer], (int)data.mtllayer.drawableSize.width, (int)data.mtllayer.drawableSize.height, data.mtllayer.pixelFormat);
|
||||||
data.mtlbackbuffer = [data.mtllayer nextDrawable];
|
mtltexture = data.mtlrealitykittexture;
|
||||||
if (load == MTLLoadActionLoad) {
|
} else
|
||||||
load = MTLLoadActionDontCare;
|
#endif
|
||||||
|
{
|
||||||
|
// Standard rendering path: use CAMetalLayer drawable
|
||||||
|
if (data.mtlbackbuffer == nil) {
|
||||||
|
// The backbuffer's contents aren't guaranteed to persist after
|
||||||
|
// presenting, so we can leave it undefined when loading it.
|
||||||
|
data.mtlbackbuffer = [data.mtllayer nextDrawable];
|
||||||
|
if (load == MTLLoadActionLoad) {
|
||||||
|
load = MTLLoadActionDontCare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.mtlbackbuffer != nil) {
|
||||||
|
mtltexture = data.mtlbackbuffer.texture;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (data.mtlbackbuffer != nil) {
|
|
||||||
mtltexture = data.mtlbackbuffer.texture;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1922,12 +1937,57 @@ static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
static id<MTLTexture> METAL_CopyToStagingTexture(SDL_Renderer *renderer, id<MTLTexture> texture, SDL_Rect *rect)
|
||||||
|
{
|
||||||
|
SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
|
||||||
|
MTLTextureDescriptor *desc;
|
||||||
|
id<MTLTexture> stagingtex;
|
||||||
|
id<MTLBlitCommandEncoder> blitcmd;
|
||||||
|
|
||||||
|
desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat
|
||||||
|
width:rect->w
|
||||||
|
height:rect->h
|
||||||
|
mipmapped:NO];
|
||||||
|
if (desc == nil) {
|
||||||
|
SDL_OutOfMemory();
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
stagingtex = [data.mtldevice newTextureWithDescriptor:desc];
|
||||||
|
if (stagingtex == nil) {
|
||||||
|
SDL_OutOfMemory();
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
|
||||||
|
|
||||||
|
[blitcmd copyFromTexture:texture
|
||||||
|
sourceSlice:0
|
||||||
|
sourceLevel:0
|
||||||
|
sourceOrigin:MTLOriginMake(rect->x, rect->y, 0)
|
||||||
|
sourceSize:MTLSizeMake(rect->w, rect->h, 1)
|
||||||
|
toTexture:stagingtex
|
||||||
|
destinationSlice:0
|
||||||
|
destinationLevel:0
|
||||||
|
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
||||||
|
|
||||||
|
[blitcmd endEncoding];
|
||||||
|
|
||||||
|
rect->x = 0;
|
||||||
|
rect->y = 0;
|
||||||
|
|
||||||
|
return stagingtex;
|
||||||
|
}
|
||||||
|
#endif // SDL_PLATFORM_VISIONOS
|
||||||
|
|
||||||
static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
|
static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
|
||||||
{
|
{
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
|
SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
|
||||||
id<MTLTexture> mtltexture;
|
id<MTLTexture> mtltexture;
|
||||||
MTLRegion mtlregion;
|
MTLRegion mtlregion;
|
||||||
|
SDL_Rect read_rect = *rect;
|
||||||
Uint32 format;
|
Uint32 format;
|
||||||
SDL_Surface *surface;
|
SDL_Surface *surface;
|
||||||
|
|
||||||
|
|
@ -1951,6 +2011,15 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
if (!renderer->target && data.mtlrealitykittexture) {
|
||||||
|
mtltexture = METAL_CopyToStagingTexture(renderer, mtltexture, &read_rect);
|
||||||
|
if (mtltexture == nil) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Commit the current command buffer and wait until it's completed, to make
|
/* Commit the current command buffer and wait until it's completed, to make
|
||||||
* sure the GPU has finished rendering to it by the time we read it. */
|
* sure the GPU has finished rendering to it by the time we read it. */
|
||||||
[data.mtlcmdbuffer commit];
|
[data.mtlcmdbuffer commit];
|
||||||
|
|
@ -1958,7 +2027,7 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||||
data.mtlcmdencoder = nil;
|
data.mtlcmdencoder = nil;
|
||||||
data.mtlcmdbuffer = nil;
|
data.mtlcmdbuffer = nil;
|
||||||
|
|
||||||
mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
|
mtlregion = MTLRegionMake2D(read_rect.x, read_rect.y, read_rect.w, read_rect.h);
|
||||||
|
|
||||||
switch (mtltexture.pixelFormat) {
|
switch (mtltexture.pixelFormat) {
|
||||||
case MTLPixelFormatBGRA8Unorm:
|
case MTLPixelFormatBGRA8Unorm:
|
||||||
|
|
@ -1991,9 +2060,16 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||||
SDL_SetError("Unknown framebuffer pixel format");
|
SDL_SetError("Unknown framebuffer pixel format");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
surface = SDL_CreateSurface(rect->w, rect->h, format);
|
surface = SDL_CreateSurface(read_rect.w, read_rect.h, format);
|
||||||
if (surface) {
|
if (surface) {
|
||||||
[mtltexture getBytes:surface->pixels bytesPerRow:surface->pitch fromRegion:mtlregion mipmapLevel:0];
|
[mtltexture getBytes:surface->pixels bytesPerRow:surface->pitch fromRegion:mtlregion mipmapLevel:0];
|
||||||
|
if (SDL_RenderingLinearSpace(renderer) &&
|
||||||
|
(!SDL_ISPIXELFORMAT_10BIT(format) && !SDL_ISPIXELFORMAT_FLOAT(format))) {
|
||||||
|
if (!SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, SDL_COLORSPACE_SRGB_LINEAR, 0, surface->pixels, surface->pitch, format, SDL_COLORSPACE_SRGB, 0, surface->pixels, surface->pitch)) {
|
||||||
|
SDL_DestroySurface(surface);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
@ -2022,8 +2098,22 @@ static bool METAL_RenderPresent(SDL_Renderer *renderer)
|
||||||
// If we don't have a drawable to present, don't try to present it.
|
// If we don't have a drawable to present, don't try to present it.
|
||||||
// But we'll still try to commit the command buffer in case it was already enqueued.
|
// But we'll still try to commit the command buffer in case it was already enqueued.
|
||||||
if (ready) {
|
if (ready) {
|
||||||
SDL_assert(data.mtlbackbuffer != nil);
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
[data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
|
if (data.mtlrealitykittexture) {
|
||||||
|
// Generate mipmaps
|
||||||
|
id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
|
||||||
|
|
||||||
|
[blitcmd generateMipmapsForTexture:data.mtlrealitykittexture];
|
||||||
|
[blitcmd endEncoding];
|
||||||
|
|
||||||
|
data.mtlrealitykittexture = nil;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
SDL_assert(data.mtlbackbuffer != nil);
|
||||||
|
[data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data.mtlcmdbuffer commit];
|
[data.mtlcmdbuffer commit];
|
||||||
|
|
@ -2057,6 +2147,11 @@ static void METAL_DestroyRenderer(SDL_Renderer *renderer)
|
||||||
[data.mtlcmdencoder endEncoding];
|
[data.mtlcmdencoder endEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.mtlcmdbuffer != nil) {
|
||||||
|
[data.mtlcmdbuffer commit];
|
||||||
|
[data.mtlcmdbuffer waitUntilCompleted];
|
||||||
|
}
|
||||||
|
|
||||||
DestroyAllPipelines(data.allpipelines, data.pipelinescount);
|
DestroyAllPipelines(data.allpipelines, data.pipelinescount);
|
||||||
|
|
||||||
/* Release the metal view instead of destroying it,
|
/* Release the metal view instead of destroying it,
|
||||||
|
|
|
||||||
410
src/video/uikit/SDL_CurvedContentHosting.swift
Normal file
410
src/video/uikit/SDL_CurvedContentHosting.swift
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
import SwiftUI
|
||||||
|
import RealityKit
|
||||||
|
import Metal
|
||||||
|
|
||||||
|
// Icons used by buttons below
|
||||||
|
|
||||||
|
// Flat button
|
||||||
|
/* SVG:
|
||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M133.333 400H666.667" stroke="black" stroke-width="66.6667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
*/
|
||||||
|
struct FlatButtonIcon : Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
let width = rect.size.width
|
||||||
|
let height = rect.size.height
|
||||||
|
var strokePath = Path()
|
||||||
|
strokePath.move(to: CGPoint(x: 0.16667*width, y: 0.5*height))
|
||||||
|
strokePath.addLine(to: CGPoint(x: 0.83333*width, y: 0.5*height))
|
||||||
|
path.addPath(strokePath.strokedPath(StrokeStyle(lineWidth: 0.08333*width, lineCap: .round, lineJoin: .round, miterLimit: 4)))
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Curved button
|
||||||
|
/* SVG:
|
||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M133 380C311 317.333 489 317.333 667 380" stroke="black" stroke-width="66.6667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
*/
|
||||||
|
struct CurvedButtonIcon : Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
let width = rect.size.width
|
||||||
|
let height = rect.size.height
|
||||||
|
var strokePath = Path()
|
||||||
|
strokePath.move(to: CGPoint(x: 0.16625*width, y: 0.475*height))
|
||||||
|
strokePath.addCurve(to: CGPoint(x: 0.83375*width, y: 0.475*height), control1: CGPoint(x: 0.38875*width, y: 0.39667*height), control2: CGPoint(x: 0.61125*width, y: 0.39667*height))
|
||||||
|
path.addPath(strokePath.strokedPath(StrokeStyle(lineWidth: 0.08333*width, lineCap: .round, lineJoin: .round, miterLimit: 4)))
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Curviest button
|
||||||
|
/* SVG:
|
||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M133 370C310.667 230 488.333 230 666 370" stroke="black" stroke-width="66.6667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
*/
|
||||||
|
struct CurviestButtonIcon : Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
let width = rect.size.width
|
||||||
|
let height = rect.size.height
|
||||||
|
var strokePath = Path()
|
||||||
|
strokePath.move(to: CGPoint(x: 0.16625*width, y: 0.4625*height))
|
||||||
|
strokePath.addCurve(to: CGPoint(x: 0.8325*width, y: 0.4625*height), control1: CGPoint(x: 0.38833*width, y: 0.2875*height), control2: CGPoint(x: 0.61042*width, y: 0.2875*height))
|
||||||
|
path.addPath(strokePath.strokedPath(StrokeStyle(lineWidth: 0.08333*width, lineCap: .round, lineJoin: .round, miterLimit: 4)))
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UIHostingController subclass that hides the visionOS glass background.
|
||||||
|
internal class SDL_ClearHostingController<Content: View>: UIHostingController<Content> {
|
||||||
|
override var preferredContainerBackgroundStyle: UIContainerBackgroundStyle {
|
||||||
|
return .hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ObjC-accessible wrapper that manages presenting SDL curved content
|
||||||
|
/// via a UIHostingController
|
||||||
|
@MainActor
|
||||||
|
@objc(SDL_CurvedContentHosting)
|
||||||
|
internal class SDL_CurvedContentHosting: NSObject {
|
||||||
|
private let settings = SDL_CurvedContentSettings()
|
||||||
|
|
||||||
|
private let helper = SDL_RealityKitHelper()
|
||||||
|
|
||||||
|
private var hostingController: SDL_ClearHostingController<SDL_CurvedContentView>?
|
||||||
|
|
||||||
|
@objc public override init() {
|
||||||
|
//NSLog("SDL_CurvedContentHosting init")
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Present the curved content view full-screen from the given view controller.
|
||||||
|
/// Uses two-phase presentation: first bootstraps the RealityView as a hidden
|
||||||
|
/// child VC, then presents modally (without animation) once content is ready.
|
||||||
|
/// Modal presentation is required on visionOS to get an independent depth budget
|
||||||
|
/// that doesn't clip curved mesh content extending forward from the window.
|
||||||
|
@objc public func present(from viewController: UIViewController) {
|
||||||
|
let contentView = SDL_CurvedContentView(helper: helper, settings: settings, onContentReady: { [weak self] in
|
||||||
|
guard let self, let hc = self.hostingController else { return }
|
||||||
|
|
||||||
|
hc.willMove(toParent: nil)
|
||||||
|
hc.view.removeFromSuperview()
|
||||||
|
hc.removeFromParent()
|
||||||
|
hc.view.layer.opacity = 1
|
||||||
|
|
||||||
|
//NSLog("SDL_CurvedContentHosting: RealityView content ready - presenting modally")
|
||||||
|
viewController.present(hc, animated: false) { [weak self] in
|
||||||
|
self?.updateOrnaments()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Spin up an async task to present / dismiss ornaments when there are updates to the scene state.
|
||||||
|
let settings = self.settings
|
||||||
|
let sceneStateObservations = Observations { [weak settings] in
|
||||||
|
guard let settings else { return nil as (SDL_CurvedContentSettings.SceneState, SDL_CurvedContentSettings.InputType, Bool, Bool)? }
|
||||||
|
return (settings.sceneState, settings.inputType, settings.isSnapped, settings.settingsExpanded)
|
||||||
|
}
|
||||||
|
Task { [weak self] in
|
||||||
|
for await _ in sceneStateObservations {
|
||||||
|
guard let self else { return }
|
||||||
|
self.updateOrnaments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hc = SDL_ClearHostingController(rootView: contentView)
|
||||||
|
hc.modalPresentationStyle = .fullScreen
|
||||||
|
hc.view.backgroundColor = .clear
|
||||||
|
hostingController = hc
|
||||||
|
|
||||||
|
hc.view.layer.opacity = 0
|
||||||
|
viewController.addChild(hc)
|
||||||
|
hc.view.frame = viewController.view.bounds
|
||||||
|
viewController.view.addSubview(hc.view)
|
||||||
|
hc.didMove(toParent: viewController)
|
||||||
|
|
||||||
|
//NSLog("SDL_CurvedContentHosting: Bootstrapping RealityView as hidden child")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateOrnaments() {
|
||||||
|
guard let hostingController else { return }
|
||||||
|
let settings = self.settings
|
||||||
|
let sceneState = settings.sceneState
|
||||||
|
UIView.animate(withDuration: 0.0) {
|
||||||
|
if sceneState == .interactive {
|
||||||
|
var sceneAnchor: UnitPoint
|
||||||
|
var contentAlignment: Alignment
|
||||||
|
if settings.isSnapped {
|
||||||
|
if settings.settingsExpanded {
|
||||||
|
sceneAnchor = .bottom
|
||||||
|
contentAlignment = .center
|
||||||
|
} else {
|
||||||
|
sceneAnchor = .bottom
|
||||||
|
contentAlignment = .top
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if settings.settingsExpanded {
|
||||||
|
sceneAnchor = .leading
|
||||||
|
contentAlignment = .center
|
||||||
|
} else {
|
||||||
|
sceneAnchor = .leading
|
||||||
|
contentAlignment = .trailing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hostingController.ornaments = [
|
||||||
|
UIHostingOrnament(sceneAnchor: sceneAnchor, contentAlignment: contentAlignment) {
|
||||||
|
SDL_SettingsPanelView(settings: settings)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
hostingController.ornaments = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display texture for this frame.
|
||||||
|
@objc public func getDisplayTexture(_ commandBuffer: MTLCommandBuffer, width: Int, height: Int, pixelFormat: MTLPixelFormat) -> MTLTexture? {
|
||||||
|
return helper.getDisplayTexture(commandBuffer, width: width, height: height, pixelFormat: pixelFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Settings Panel
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
internal class SDL_CurvedContentSettings {
|
||||||
|
/// State of the app user interface, determined by the content view's state.
|
||||||
|
enum SceneState {
|
||||||
|
/// A state which allows the user to configure the scene. Ornaments should be visible.
|
||||||
|
case interactive
|
||||||
|
|
||||||
|
/// A state which hides all UI except for the game itself. Ornaments should not be visible.
|
||||||
|
case cinematic
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InputType {
|
||||||
|
case eyes
|
||||||
|
case pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputType: InputType = .eyes
|
||||||
|
var showHover: Bool = true
|
||||||
|
var isDimmed: Bool = false
|
||||||
|
var curvatureRadius: Float = SDL_VisionOS_GetCurvature()
|
||||||
|
var sceneState: SceneState = .interactive
|
||||||
|
var isSnapped: Bool = false
|
||||||
|
var settingsExpanded: Bool = false
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SDL_SettingsPanelView: View {
|
||||||
|
let settings: SDL_CurvedContentSettings
|
||||||
|
@State private var curvatureSlider: Float = 0.0
|
||||||
|
|
||||||
|
static let minimumCurvatureRadius: Float = 800.0
|
||||||
|
static let maximumCurvatureRadius: Float = 4500.0
|
||||||
|
|
||||||
|
static let curvatureSteps: [Float] = [
|
||||||
|
0,
|
||||||
|
4000,
|
||||||
|
3000,
|
||||||
|
2300,
|
||||||
|
1800,
|
||||||
|
1500,
|
||||||
|
1000,
|
||||||
|
800
|
||||||
|
]
|
||||||
|
|
||||||
|
static let curvatureStepsSliderValue: [Float] = curvatureSteps.map {
|
||||||
|
if $0 <= 0.01 {
|
||||||
|
return 0 // flat
|
||||||
|
}
|
||||||
|
return 1.0 - ($0 - minimumCurvatureRadius) / (maximumCurvatureRadius - minimumCurvatureRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var curvatureLabel: String {
|
||||||
|
if settings.curvatureRadius > 0 {
|
||||||
|
return "\(Int(settings.curvatureRadius))R"
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if settings.settingsExpanded {
|
||||||
|
expandedPanel
|
||||||
|
} else {
|
||||||
|
collapsedBar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Collapsed
|
||||||
|
|
||||||
|
private var collapsedBar: some View {
|
||||||
|
Button(action: { withAnimation { settings.settingsExpanded = true } }) {
|
||||||
|
if settings.isSnapped {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Image(systemName: settings.showHover ? "eye" : "eye.slash")
|
||||||
|
|
||||||
|
Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
|
||||||
|
.foregroundStyle(settings.isDimmed ? .primary : .secondary)
|
||||||
|
|
||||||
|
Divider().frame(height: 8)
|
||||||
|
|
||||||
|
if settings.curvatureRadius == 0 {
|
||||||
|
FlatButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else if settings.curvatureRadius > 1000.0 {
|
||||||
|
CurvedButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else {
|
||||||
|
CurviestButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
} else {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Image(systemName: settings.showHover ? "eye" : "eye.slash")
|
||||||
|
|
||||||
|
Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
|
||||||
|
.foregroundStyle(settings.isDimmed ? .primary : .secondary)
|
||||||
|
|
||||||
|
Divider().frame(height: 8)
|
||||||
|
|
||||||
|
if settings.curvatureRadius == 0 {
|
||||||
|
FlatButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else if settings.curvatureRadius > 1000.0 {
|
||||||
|
CurvedButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else {
|
||||||
|
CurviestButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.glassBackgroundEffect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Expanded
|
||||||
|
|
||||||
|
private var expandedPanel: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
// Input type and dim controls
|
||||||
|
@Bindable var settings = self.settings
|
||||||
|
|
||||||
|
Text("").font(.title).padding(8)
|
||||||
|
|
||||||
|
HStack() {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "eye.slash")
|
||||||
|
|
||||||
|
Toggle(isOn: $settings.showHover) {
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.tint(.secondary)
|
||||||
|
|
||||||
|
Image(systemName: "eye")
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "sun.max")
|
||||||
|
|
||||||
|
Toggle(isOn: $settings.isDimmed) {
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.tint(.secondary)
|
||||||
|
|
||||||
|
Image(systemName: "moon.fill")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Curvature slider
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Text("\(curvatureLabel)")
|
||||||
|
.font(.caption)
|
||||||
|
|
||||||
|
HStack() {
|
||||||
|
FlatButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
|
||||||
|
Slider(value: $curvatureSlider, in: 0...1) {
|
||||||
|
} currentValueLabel: {
|
||||||
|
Text("\(curvatureLabel)")
|
||||||
|
} ticks: {
|
||||||
|
SliderTickContentForEach(Self.curvatureStepsSliderValue, id: \.self) { value in
|
||||||
|
SliderTick(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
let curvature = settings.curvatureRadius
|
||||||
|
if curvature > 0 {
|
||||||
|
curvatureSlider = 1.0 - (curvature - Self.minimumCurvatureRadius)
|
||||||
|
/ (Self.maximumCurvatureRadius - Self.minimumCurvatureRadius)
|
||||||
|
} else {
|
||||||
|
curvatureSlider = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: curvatureSlider) {
|
||||||
|
let clamped = max(0.0, min(1.0, curvatureSlider))
|
||||||
|
if clamped == 0 {
|
||||||
|
settings.curvatureRadius = 0
|
||||||
|
} else {
|
||||||
|
let radius = roundf(curvatureSlider * Self.minimumCurvatureRadius
|
||||||
|
+ (1.0 - curvatureSlider) * Self.maximumCurvatureRadius)
|
||||||
|
settings.curvatureRadius = radius
|
||||||
|
}
|
||||||
|
SDL_VisionOS_SendCurvatureChanged(settings.curvatureRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
CurviestButtonIcon()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.frame(width: 340)
|
||||||
|
.overlay(alignment: .topLeading) {
|
||||||
|
// X button
|
||||||
|
Button(action: { withAnimation { settings.settingsExpanded = false } }) {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
.font(.system(size: 15, weight: .bold, design: .rounded))
|
||||||
|
.padding(8)
|
||||||
|
.contentShape(Circle())
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.buttonBorderShape(.circle)
|
||||||
|
.padding(20)
|
||||||
|
}
|
||||||
|
.glassBackgroundEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
350
src/video/uikit/SDL_CurvedContentView.swift
Normal file
350
src/video/uikit/SDL_CurvedContentView.swift
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
import SwiftUI
|
||||||
|
import RealityKit
|
||||||
|
import GameController
|
||||||
|
|
||||||
|
/// SwiftUI view that presents SDL content on a curved RealityKit mesh
|
||||||
|
/// inside a UIHostingController
|
||||||
|
internal struct SDL_CurvedContentView: View {
|
||||||
|
/// Helper object used to manage the mesh and texture of the curved UI.
|
||||||
|
let helper: SDL_RealityKitHelper
|
||||||
|
|
||||||
|
/// Settings object provided by the caller which determines the UI state.
|
||||||
|
let settings: SDL_CurvedContentSettings
|
||||||
|
|
||||||
|
/// Information about the window snap status
|
||||||
|
@Environment(\.surfaceSnappingInfo) private var snappedStatus
|
||||||
|
|
||||||
|
/// Closure which is called when the content is ready to present.
|
||||||
|
let onContentReady: @MainActor () -> Void
|
||||||
|
|
||||||
|
/// RealityKit entity which is created on appear, to be populated by the curved UI content.
|
||||||
|
@State private var curvedUIEntity: ModelEntity! = nil
|
||||||
|
|
||||||
|
/// Curved UI material which is created on appear. Holds the compiled shader and material parameters.
|
||||||
|
@State private var curvedUIMaterial: CurvedUIMaterial! = nil
|
||||||
|
|
||||||
|
/// Converts SwiftUI points to meters (RealityKit coordinates)
|
||||||
|
///
|
||||||
|
/// - Note: This conversion varies depending on the physical distance between the window and the user.
|
||||||
|
@PhysicalMetric(from: .meters) private var pointsPerMeter: Float = 1
|
||||||
|
|
||||||
|
/// Inverse of ``pointsPerMeter``.
|
||||||
|
var metersPerPoint: Float { 1.0 / pointsPerMeter }
|
||||||
|
|
||||||
|
/// The cursor color which should be passed to `curvedUIMaterial`
|
||||||
|
@State private var cursorColor: UIColor = .lightGray
|
||||||
|
|
||||||
|
/// The cursor color on interact (pinch/drag/click) which should be passed to `curvedUIMaterial`
|
||||||
|
@State private var cursorColorOnInteract: UIColor = .systemCyan
|
||||||
|
|
||||||
|
/// Whether to show the cursor overlay on the mesh surface.
|
||||||
|
private var showCursor: Bool {
|
||||||
|
return !mouseInputEnabled && settings.showHover
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether mouse input is enabled. When this is the case, the collision shape for indirect input should be disabled.
|
||||||
|
private var mouseInputEnabled: Bool {
|
||||||
|
return settings.inputType == .pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shouldPopulateCollisionShape: Bool {
|
||||||
|
return curvedUIEntity != nil && helper.collisionShape != nil && !mouseInputEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value use to animate the screen radius
|
||||||
|
@State private var animatedScreenRadius: Float = 1010
|
||||||
|
|
||||||
|
let SDL_EVENT_FINGER_DOWN: UInt32 = 0x700
|
||||||
|
let SDL_EVENT_FINGER_UP: UInt32 = 0x701
|
||||||
|
let SDL_EVENT_FINGER_MOTION: UInt32 = 0x702
|
||||||
|
let SDL_EVENT_FINGER_CANCELED: UInt32 = 0x703
|
||||||
|
private(set) static var last_fingerID: UInt64 = 0
|
||||||
|
private(set) static var fingers: [SpatialEventCollection.Event.ID: UInt64] = [:]
|
||||||
|
|
||||||
|
private func sendTouchEvent(event: SpatialEventCollection.Event, proxy: GeometryProxy3D) {
|
||||||
|
var fingerID: UInt64
|
||||||
|
var eventType: UInt32
|
||||||
|
if let value = Self.fingers[event.id] {
|
||||||
|
fingerID = value
|
||||||
|
if event.phase == SpatialEventCollection.Event.Phase.active {
|
||||||
|
eventType = SDL_EVENT_FINGER_MOTION
|
||||||
|
} else if event.phase == SpatialEventCollection.Event.Phase.ended {
|
||||||
|
eventType = SDL_EVENT_FINGER_UP
|
||||||
|
Self.fingers.removeValue(forKey: event.id)
|
||||||
|
} else {
|
||||||
|
eventType = SDL_EVENT_FINGER_CANCELED
|
||||||
|
Self.fingers.removeValue(forKey: event.id)
|
||||||
|
}
|
||||||
|
} else if event.phase == SpatialEventCollection.Event.Phase.active {
|
||||||
|
Self.last_fingerID += 1
|
||||||
|
fingerID = Self.last_fingerID
|
||||||
|
Self.fingers[event.id] = fingerID
|
||||||
|
eventType = SDL_EVENT_FINGER_DOWN
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let loc = Point3D(x: event.location3D.x - proxy.size.width / 2,
|
||||||
|
y: event.location3D.y - proxy.size.height / 2,
|
||||||
|
z: event.location3D.z - proxy.size.depth / 2)
|
||||||
|
let meshPos = SIMD3<Float>(Float(loc.x) * metersPerPoint,
|
||||||
|
Float(loc.y) * metersPerPoint,
|
||||||
|
Float(loc.z) * metersPerPoint)
|
||||||
|
let uv = helper.meshGeometry.normalizedUV(fromMeshPosition: meshPos)
|
||||||
|
|
||||||
|
SDL_VisionOS_SendTouch(event.timestamp, fingerID, eventType, uv.x, uv.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader3D { proxy in
|
||||||
|
realityContent(proxy)
|
||||||
|
.glassBackgroundEffect(displayMode: .never)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func realityContent(_ proxy: GeometryProxy3D) -> some View {
|
||||||
|
RealityView { content in
|
||||||
|
//NSLog("SDL_CurvedContentView: RealityView setup")
|
||||||
|
|
||||||
|
let frameInMeters: BoundingBox = content.convert(proxy.frame(in: .local), from: .local, to: .scene)
|
||||||
|
helper.updateMeshSize(width: frameInMeters.extents.x, height: frameInMeters.extents.y)
|
||||||
|
|
||||||
|
// Compile curved UI shader (may take a while)
|
||||||
|
let material = try! await CurvedUIMaterial()
|
||||||
|
self.curvedUIMaterial = material
|
||||||
|
|
||||||
|
// Create RealityKit Entity to host the curved UI content
|
||||||
|
let mesh = try! await MeshResource(from: helper.lowLevelMesh)
|
||||||
|
let entity = ModelEntity(mesh: mesh, materials: [material.shaderGraphMaterial])
|
||||||
|
|
||||||
|
// Add InputTargetComponent to the mesh to accept input.
|
||||||
|
entity.components.set(InputTargetComponent(allowedInputTypes: .all))
|
||||||
|
|
||||||
|
// Add HoverEffectComponent to visualize the gaze target
|
||||||
|
let shaderInputs = HoverEffectComponent.ShaderHoverEffectInputs.default
|
||||||
|
let hoverEffect = HoverEffectComponent.HoverEffect.shader(shaderInputs)
|
||||||
|
let hoverEffectComponent = HoverEffectComponent(hoverEffect)
|
||||||
|
entity.components.set(hoverEffectComponent)
|
||||||
|
|
||||||
|
// Increase the responsiveness of the hover effect
|
||||||
|
RenderRefreshSystem.registerSystem()
|
||||||
|
entity.components.set(RenderRefreshComponent(
|
||||||
|
componentToRefresh: hoverEffectComponent
|
||||||
|
))
|
||||||
|
|
||||||
|
self.curvedUIEntity = entity
|
||||||
|
content.add(entity)
|
||||||
|
|
||||||
|
// Call the user-provided contentReady closure.
|
||||||
|
onContentReady()
|
||||||
|
} update: { content in
|
||||||
|
let frameInMeters: BoundingBox = content.convert(proxy.frame(in: .local), from: .local, to: .scene)
|
||||||
|
helper.updateMeshSize(width: frameInMeters.extents.x, height: frameInMeters.extents.y)
|
||||||
|
|
||||||
|
let frame = proxy.frame(in: .local)
|
||||||
|
SDL_VisionOS_SendSizeChanged(Int(frame.size.width), Int(frame.size.height))
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
if mouseInputEnabled {
|
||||||
|
// This enables mouse motion events, but blocks hover location
|
||||||
|
Color.white
|
||||||
|
.opacity(0.001)
|
||||||
|
.pointerStyle(.shape(Circle(), size: .zero))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gesture(
|
||||||
|
SpatialEventGesture()
|
||||||
|
.onChanged { events in
|
||||||
|
guard curvedUIMaterial != nil else { return }
|
||||||
|
|
||||||
|
if !mouseInputEnabled {
|
||||||
|
curvedUIMaterial.isInteracting = true
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
if event.kind != .pointer {
|
||||||
|
sendTouchEvent(event: event, proxy: proxy)
|
||||||
|
} else {
|
||||||
|
settings.inputType = .pointer
|
||||||
|
settings.sceneState = .cinematic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEnded { events in
|
||||||
|
guard curvedUIMaterial != nil else { return }
|
||||||
|
|
||||||
|
if !mouseInputEnabled {
|
||||||
|
for event in events {
|
||||||
|
if event.kind != .pointer {
|
||||||
|
sendTouchEvent(event: event, proxy: proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for event in events {
|
||||||
|
if event.kind != .pointer {
|
||||||
|
settings.inputType = .eyes
|
||||||
|
settings.sceneState = .interactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curvedUIMaterial.isInteracting = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onChange(of: sceneActivationOrObject(showCursor), initial: true) {
|
||||||
|
curvedUIMaterial?.showCursor = showCursor
|
||||||
|
}
|
||||||
|
.onChange(of: sceneActivationOrObject(cursorColor), initial: true) {
|
||||||
|
curvedUIMaterial?.cursorColor = cursorColor
|
||||||
|
}
|
||||||
|
.onChange(of: sceneActivationOrObject(cursorColorOnInteract), initial: true) {
|
||||||
|
curvedUIMaterial?.cursorColorOnInteract = cursorColorOnInteract
|
||||||
|
}
|
||||||
|
.onChange(of: sceneActivationOrObject(helper.meshGeometry), initial: true) {
|
||||||
|
guard curvedUIMaterial != nil else { return }
|
||||||
|
let geometry = helper.meshGeometry
|
||||||
|
curvedUIMaterial.cursorSize = geometry.height * 0.01
|
||||||
|
}
|
||||||
|
.onChange(of: sceneActivationOrObject(helper.textureResource), initial: true) {
|
||||||
|
if let textureResource = helper.textureResource {
|
||||||
|
curvedUIMaterial?.gameTexture = textureResource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: sceneActivationOrObject(curvedUIMaterial), initial: true) {
|
||||||
|
// Update the materials array of the entity with the updated material parameters.
|
||||||
|
if let curvedUIMaterial, let curvedUIEntity {
|
||||||
|
curvedUIEntity.model!.materials = [curvedUIMaterial.shaderGraphMaterial]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: settings.inputType, initial: true) { oldInputType, inputType in
|
||||||
|
if inputType == .pointer {
|
||||||
|
SDL_VisionOS_SendPointerMode(true)
|
||||||
|
} else {
|
||||||
|
SDL_VisionOS_SendPointerMode(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: settings.curvatureRadius, initial: true) { oldRadius, curvatureRadius in
|
||||||
|
if oldRadius != curvatureRadius {
|
||||||
|
withAnimation(.smooth) {
|
||||||
|
if curvatureRadius > 0 {
|
||||||
|
animatedScreenRadius = curvatureRadius / 1000
|
||||||
|
} else {
|
||||||
|
animatedScreenRadius = AnimatedCurveRadiusModifier.assumedFlatThreshold + 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if curvatureRadius > 0 {
|
||||||
|
animatedScreenRadius = curvatureRadius / 1000
|
||||||
|
} else {
|
||||||
|
animatedScreenRadius = AnimatedCurveRadiusModifier.assumedFlatThreshold + 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modifier(AnimatedCurveRadiusModifier(helper: helper, curveRadius: animatedScreenRadius))
|
||||||
|
.onChange(of: sceneActivationOrObject(shouldPopulateCollisionShape ? helper.collisionShape : nil)) {
|
||||||
|
guard let curvedUIEntity else { return }
|
||||||
|
if let shape = helper.collisionShape, shouldPopulateCollisionShape {
|
||||||
|
curvedUIEntity.components.set(CollisionComponent(shapes: [shape]))
|
||||||
|
} else {
|
||||||
|
curvedUIEntity.components.set(CollisionComponent(shapes: []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: snappedStatus) {
|
||||||
|
settings.isSnapped = snappedStatus.isSnapped
|
||||||
|
helper.updateSnappedStatus(snapped: snappedStatus.isSnapped)
|
||||||
|
}
|
||||||
|
.preferredSurroundingsEffect(settings.isDimmed ? .dark : nil)
|
||||||
|
.frame(depth: 0)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.persistentSystemOverlays(settings.sceneState == .cinematic ? .hidden : .automatic)
|
||||||
|
.handlesGameControllerEvents(matching: .gamepad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Animating the curve radius
|
||||||
|
|
||||||
|
@Animatable
|
||||||
|
private struct AnimatedCurveRadiusModifier: @MainActor ViewModifier {
|
||||||
|
/// Curvature radius beyond which we assume it is flat.
|
||||||
|
static let assumedFlatThreshold: Float = 30.0
|
||||||
|
|
||||||
|
/// Helper object to modify
|
||||||
|
let helper: SDL_RealityKitHelper
|
||||||
|
|
||||||
|
/// Curve radius > `assumedFlatThreshold` meters is assumed to be flat.
|
||||||
|
var curveRadius: Float
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content.onChange(of: curveRadius, initial: true) {
|
||||||
|
if curveRadius > 10 {
|
||||||
|
helper.updateMeshCurvature(curvatureRadius: 0)
|
||||||
|
} else {
|
||||||
|
helper.updateMeshCurvature(curvatureRadius: curveRadius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Bridging SwiftUI and RealityKit
|
||||||
|
|
||||||
|
private extension SDL_CurvedContentView {
|
||||||
|
private struct Box<T: Equatable>: Equatable {
|
||||||
|
var sceneActivation: Bool
|
||||||
|
var value: T
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function which triggers an `onChange` event either when `object` changes, or when
|
||||||
|
/// ``curvedUIMaterial`` finishes compiling.
|
||||||
|
func sceneActivationOrObject<T: Equatable>(_ object: T) -> some Equatable {
|
||||||
|
return Box(sceneActivation: self.curvedUIMaterial != nil && self.curvedUIEntity != nil, value: object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Per-frame component refresh
|
||||||
|
|
||||||
|
/// Attach this component to an entity to reset a RealityKit component every rendering frame.
|
||||||
|
/// This can be used to disable system-default interpolation on any component that applies it.
|
||||||
|
///
|
||||||
|
/// Example — to reset a platform-specific component every frame:
|
||||||
|
/// entity.components.set(RenderRefreshComponent(
|
||||||
|
/// componentToRefresh: CustomComponent()
|
||||||
|
/// ))
|
||||||
|
private struct RenderRefreshComponent: TransientComponent {
|
||||||
|
var componentToRefresh: (any Component)?
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RenderRefreshSystem: System {
|
||||||
|
static let query = EntityQuery(where: .has(RenderRefreshComponent.self))
|
||||||
|
init(scene: RealityKit.Scene) {
|
||||||
|
RenderRefreshComponent.registerComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(context: SceneUpdateContext) {
|
||||||
|
for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) {
|
||||||
|
guard let refresh = entity.components[RenderRefreshComponent.self],
|
||||||
|
let component = refresh.componentToRefresh else { continue }
|
||||||
|
entity.components.remove(type(of: component))
|
||||||
|
entity.components.set(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
544
src/video/uikit/SDL_CurvedUIShader.swift
Normal file
544
src/video/uikit/SDL_CurvedUIShader.swift
Normal file
|
|
@ -0,0 +1,544 @@
|
||||||
|
//
|
||||||
|
// SDL_CurvedUIShader.swift
|
||||||
|
// SDL3
|
||||||
|
//
|
||||||
|
// Created by Adrian Biagioli on 4/21/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RealityKit
|
||||||
|
|
||||||
|
/// A MaterialX curved UI shader USDA. This is loaded on launch into a ShaderGraphMaterial.
|
||||||
|
///
|
||||||
|
/// You can inspect this shader yourself in Reality Composer Pro.
|
||||||
|
/// To do this, copy this string and save it as a .usda file.
|
||||||
|
/// Then, add it to a Reality Composer Pro object.
|
||||||
|
private let curvedUIShaderUSDA = """
|
||||||
|
#usda 1.0
|
||||||
|
(
|
||||||
|
customLayerData = {
|
||||||
|
string creator = "Reality Composer Pro Version 2.0 (494.100.6)"
|
||||||
|
}
|
||||||
|
defaultPrim = "Root"
|
||||||
|
metersPerUnit = 1
|
||||||
|
upAxis = "Y"
|
||||||
|
)
|
||||||
|
|
||||||
|
def Xform "Root"
|
||||||
|
{
|
||||||
|
def Material "CurvedUIMaterial"
|
||||||
|
{
|
||||||
|
reorder nameChildren = ["DefaultSurfaceShader", "UnlitSurface", "TextureCoordinates", "Position", "Image2D", "Group2", "Group4", "CursorPositionOnScreen", "SelectCursorColor", "SelectCursorOpacity", "GameTextureRGB", "NormalizedDistance", "Dot", "Group", "Dot_1", "DiscardCursorOutsideRange", "MixCursorOverGame", "HideCursorIfDisabled"]
|
||||||
|
color3f inputs:CursorColor = (0, 0.87658346, 1) (
|
||||||
|
colorSpace = "lin_srgb"
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-374.2671, 402.7502)
|
||||||
|
int stackingOrderInSubgraph = 1955
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
color3f inputs:CursorColorOnInteract = (0.016926037, 0, 0.7703071) (
|
||||||
|
colorSpace = "lin_srgb"
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-408.82837, 336.09396)
|
||||||
|
int stackingOrderInSubgraph = 2017
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float inputs:CursorEdgeThreshold = 0.9 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-706.12756, 582.3273)
|
||||||
|
int stackingOrderInSubgraph = 1951
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float inputs:CursorOpacityEdge = 0.7 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-704.3221, 648.0528)
|
||||||
|
int stackingOrderInSubgraph = 1953
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float inputs:CursorOpacityInside = 0.4 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-701.167, 710.96765)
|
||||||
|
int stackingOrderInSubgraph = 1955
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float inputs:CursorSize = 0.003 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-1204.8192, 509.2949)
|
||||||
|
int stackingOrderInSubgraph = 2015
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
asset inputs:GameTexture (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-1270.7656, -315.35458)
|
||||||
|
int stackingOrderInSubgraph = 1834
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bool inputs:IsInteracting = 0 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-373.38513, 263.61777)
|
||||||
|
int stackingOrderInSubgraph = 1955
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bool inputs:ShowCursor = 1 (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (-1721.0664, 367.89142)
|
||||||
|
int stackingOrderInSubgraph = 2360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
token outputs:mtlx:surface.connect = </Root/CurvedUIMaterial/UnlitSurface.outputs:out>
|
||||||
|
token outputs:realitykit:vertex
|
||||||
|
token outputs:surface.connect = </Root/CurvedUIMaterial/DefaultSurfaceShader.outputs:surface>
|
||||||
|
float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (612.1894, 109.99387)
|
||||||
|
int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 1993
|
||||||
|
|
||||||
|
def Shader "DefaultSurfaceShader" (
|
||||||
|
active = false
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uniform token info:id = "UsdPreviewSurface"
|
||||||
|
color3f inputs:diffuseColor = (1, 1, 1)
|
||||||
|
float inputs:roughness = 0.75
|
||||||
|
token outputs:surface
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "UnlitSurface"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_realitykit_unlit_surfaceshader"
|
||||||
|
bool inputs:applyPostProcessToneMap = 0
|
||||||
|
color3f inputs:color.connect = </Root/CurvedUIMaterial/MixCursorOverGame.outputs:out>
|
||||||
|
bool inputs:hasPremultipliedAlpha
|
||||||
|
float inputs:opacity
|
||||||
|
float inputs:opacityThreshold
|
||||||
|
token outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (368.7634, 58.4275)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1993
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "TextureCoordinates"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_texcoord_vector2"
|
||||||
|
float2 outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-1292.3005, -120.02362)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1834
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "Position"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_position_vector3"
|
||||||
|
string inputs:space = "world"
|
||||||
|
float3 outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-1205.6492, 445.2142)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 2314
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "Image2D"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_RealityKitTexture2D_color4"
|
||||||
|
float inputs:bias
|
||||||
|
string inputs:border_color
|
||||||
|
float inputs:dynamic_min_lod_clamp
|
||||||
|
asset inputs:file.connect = </Root/CurvedUIMaterial.inputs:GameTexture>
|
||||||
|
bool inputs:no_flip_v = 1
|
||||||
|
int2 inputs:offset
|
||||||
|
float2 inputs:texcoord.connect = </Root/CurvedUIMaterial/TextureCoordinates.outputs:out>
|
||||||
|
string inputs:u_wrap_mode
|
||||||
|
string inputs:v_wrap_mode
|
||||||
|
color4f outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-1023.8389, -194.1174)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1834
|
||||||
|
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:no_flip_v"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def Scope "Group2" (
|
||||||
|
kind = "group"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string ui:group:annotation = "Apply final color to UnlitMaterial"
|
||||||
|
string ui:group:annotationDescription = ""
|
||||||
|
string[] ui:group:members = ["p:UnlitSurface", "o:_subgraphOutput"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def Scope "Group4" (
|
||||||
|
kind = "group"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string ui:group:annotation = "Sample game texture"
|
||||||
|
string ui:group:annotationDescription = ""
|
||||||
|
string[] ui:group:members = ["i:inputs:GameTexture", "p:Image2D", "p:TextureCoordinates"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "SelectCursorColor"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_ifequal_color3B"
|
||||||
|
color3f inputs:in1.connect = </Root/CurvedUIMaterial.inputs:CursorColorOnInteract>
|
||||||
|
color3f inputs:in2.connect = </Root/CurvedUIMaterial.inputs:CursorColor>
|
||||||
|
bool inputs:value1.connect = </Root/CurvedUIMaterial.inputs:IsInteracting>
|
||||||
|
bool inputs:value2 = 1
|
||||||
|
color3f outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-175.6293, 330.2353)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1955
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "SelectCursorOpacity"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_ifgreater_float"
|
||||||
|
float inputs:in1.connect = </Root/CurvedUIMaterial.inputs:CursorOpacityEdge>
|
||||||
|
float inputs:in2.connect = </Root/CurvedUIMaterial.inputs:CursorOpacityInside>
|
||||||
|
float inputs:value1.connect = </Root/CurvedUIMaterial/Dot.outputs:out>
|
||||||
|
float inputs:value2.connect = </Root/CurvedUIMaterial.inputs:CursorEdgeThreshold>
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-463.96164, 578.08826)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1853
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "GameTextureRGB"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_swizzle_color4_color3"
|
||||||
|
string inputs:channels = "rgb"
|
||||||
|
color4f inputs:in.connect = </Root/CurvedUIMaterial/Image2D.outputs:out>
|
||||||
|
color3f outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-732.1035, -11.733684)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1834
|
||||||
|
}
|
||||||
|
|
||||||
|
def NodeGraph "NormalizedDistance"
|
||||||
|
{
|
||||||
|
float3 inputs:A (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (79.30469, 187.10547)
|
||||||
|
int stackingOrderInSubgraph = 1406
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float3 inputs:A.connect = </Root/CurvedUIMaterial/HideCursorIfDisabled.outputs:out>
|
||||||
|
float3 inputs:B (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (79.234375, 270.22266)
|
||||||
|
int stackingOrderInSubgraph = 1408
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float3 inputs:B.connect = </Root/CurvedUIMaterial/Position.outputs:out>
|
||||||
|
float inputs:Radius (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (306.85156, 333.83984)
|
||||||
|
int stackingOrderInSubgraph = 1406
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float inputs:Radius.connect = </Root/CurvedUIMaterial.inputs:CursorSize>
|
||||||
|
float outputs:ZeroToOneDistance (
|
||||||
|
customData = {
|
||||||
|
dictionary realitykit = {
|
||||||
|
float2 positionInSubgraph = (444.625, 223)
|
||||||
|
int stackingOrderInSubgraph = 1409
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
float outputs:ZeroToOneDistance.connect = </Root/CurvedUIMaterial/NormalizedDistance/Remap.outputs:out>
|
||||||
|
float2 ui:nodegraph:node:pos = (-998.9227, 417.7417)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 2010
|
||||||
|
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["outputs:Clamp_out", "inputs:A"]
|
||||||
|
float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (711.2656, 366.07812)
|
||||||
|
int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 1409
|
||||||
|
|
||||||
|
def Shader "Remap"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_remap_float"
|
||||||
|
float inputs:in.connect = </Root/CurvedUIMaterial/NormalizedDistance/MTLDistance.outputs:out>
|
||||||
|
float inputs:inhigh.connect = </Root/CurvedUIMaterial/NormalizedDistance.inputs:Radius>
|
||||||
|
float inputs:inlow = 0
|
||||||
|
float inputs:outhigh = 1
|
||||||
|
float inputs:outlow = 0
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (503, 318.58984)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1407
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "MTLDistance"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_MTL_distance_vector3_float"
|
||||||
|
float3 inputs:x.connect = </Root/CurvedUIMaterial/NormalizedDistance.inputs:A>
|
||||||
|
float3 inputs:y.connect = </Root/CurvedUIMaterial/NormalizedDistance.inputs:B>
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (304, 186.67969)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1402
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "Dot"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_dot_float"
|
||||||
|
float inputs:in.connect = </Root/CurvedUIMaterial/NormalizedDistance.outputs:ZeroToOneDistance>
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-626.7584, 475.93542)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1735
|
||||||
|
}
|
||||||
|
|
||||||
|
def Scope "Group" (
|
||||||
|
kind = "group"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string ui:group:annotation = "Select cursor color and opacity"
|
||||||
|
string ui:group:annotationDescription = "The color is selected depending if the user is interacting (click/tap/pinch/drag). The opacity is selected via the distance between this fragment's position and the cursor position"
|
||||||
|
string[] ui:group:members = ["i:inputs:IsInteracting", "p:Dot_1", "p:DiscardCursorOutsideRange", "i:inputs:CursorColorOnInteract", "p:SelectCursorColor", "i:inputs:CursorColor", "p:Dot", "i:inputs:CursorOpacityEdge", "i:inputs:CursorOpacityInside", "p:SelectCursorOpacity", "i:inputs:CursorEdgeThreshold"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "Dot_1"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_dot_float"
|
||||||
|
float inputs:in.connect = </Root/CurvedUIMaterial/Dot.outputs:out>
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-370.1385, 475.2281)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1851
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "DiscardCursorOutsideRange"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_ifgreater_float"
|
||||||
|
float inputs:in1 = 0
|
||||||
|
float inputs:in2.connect = </Root/CurvedUIMaterial/SelectCursorOpacity.outputs:out>
|
||||||
|
float inputs:value1.connect = </Root/CurvedUIMaterial/Dot_1.outputs:out>
|
||||||
|
float inputs:value2 = 1
|
||||||
|
float outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-192.05971, 600.1504)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1966
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "MixCursorOverGame"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_mix_color3"
|
||||||
|
color3f inputs:bg.connect = </Root/CurvedUIMaterial/GameTextureRGB.outputs:out>
|
||||||
|
color3f inputs:fg.connect = </Root/CurvedUIMaterial/SelectCursorColor.outputs:out>
|
||||||
|
float inputs:mix.connect = </Root/CurvedUIMaterial/DiscardCursorOutsideRange.outputs:out>
|
||||||
|
color3f outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (90.70218, -17.587646)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 1973
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "HideCursorIfDisabled"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_ifequal_vector3B"
|
||||||
|
float3 inputs:in1.connect = </Root/CurvedUIMaterial/HoverState.outputs:position>
|
||||||
|
float3 inputs:in2 = (999999, 999999, 999999)
|
||||||
|
bool inputs:value1.connect = </Root/CurvedUIMaterial/And.outputs:out>
|
||||||
|
bool inputs:value2 = 1
|
||||||
|
bool inputs:value2.connect = None
|
||||||
|
float3 outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-1281.8472, 322.0585)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 2361
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "HoverState"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_realitykit_hover_state"
|
||||||
|
float outputs:intensity
|
||||||
|
bool outputs:isActive
|
||||||
|
float3 outputs:position
|
||||||
|
float outputs:timeSinceHoverStart
|
||||||
|
float2 ui:nodegraph:node:pos = (-1730.769, 258.70575)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 2360
|
||||||
|
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["outputs:position"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def Shader "And"
|
||||||
|
{
|
||||||
|
uniform token info:id = "ND_realitykit_logical_and"
|
||||||
|
bool inputs:in1.connect = </Root/CurvedUIMaterial/HoverState.outputs:isActive>
|
||||||
|
bool inputs:in2.connect = </Root/CurvedUIMaterial.inputs:ShowCursor>
|
||||||
|
bool outputs:out
|
||||||
|
float2 ui:nodegraph:node:pos = (-1571.7467, 334.56076)
|
||||||
|
int ui:nodegraph:node:stackingOrder = 2360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
/// A wrapper object around a RealityKit `ShaderGraphMaterial`, but specific to the SDL curved UI shader.
|
||||||
|
///
|
||||||
|
/// This struct provides material parameters that pass through to the `ShaderGraphMaterial`.
|
||||||
|
@MainActor
|
||||||
|
struct CurvedUIMaterial: @MainActor Equatable {
|
||||||
|
/// A cached ShaderGraphMaterial, populated with a prototype ShaderGraphMaterial.
|
||||||
|
///
|
||||||
|
/// On subsequent loads, the alread-loaded material is used directly.
|
||||||
|
@MainActor private static var cachedShaderGraph: ShaderGraphMaterial?
|
||||||
|
|
||||||
|
/// The ShaderGraphMaterial which should be used to populate the curved UI Entity's `ModelComponent`.
|
||||||
|
///
|
||||||
|
/// - Note: ShaderGraphMaterial is a value type (`struct`), so you must re-query this value after changing any parameters.
|
||||||
|
private(set) var shaderGraphMaterial: ShaderGraphMaterial
|
||||||
|
|
||||||
|
/// Initializes the curved UI material.
|
||||||
|
///
|
||||||
|
/// If the shader needs to compile (first launch), then it compiles before returning.
|
||||||
|
/// If the shader is already compiled, returns immediately.
|
||||||
|
@MainActor
|
||||||
|
init() async throws {
|
||||||
|
if let cachedShaderGraph = Self.cachedShaderGraph {
|
||||||
|
self.shaderGraphMaterial = cachedShaderGraph
|
||||||
|
} else {
|
||||||
|
let result = try await ShaderGraphMaterial(
|
||||||
|
named: "/Root/CurvedUIMaterial",
|
||||||
|
from: Data(curvedUIShaderUSDA.utf8)
|
||||||
|
)
|
||||||
|
Self.cachedShaderGraph = result
|
||||||
|
self.shaderGraphMaterial = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The texture containing SDL content.
|
||||||
|
var gameTexture: TextureResource! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.gameTexture) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.gameTexture, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Color of the cursor overlay when not actively interacting.
|
||||||
|
var cursorColor: UIColor! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.cursorColor) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.cursorColor, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Color of the cursor when interacting (click/tap/pinch/drag)
|
||||||
|
var cursorColorOnInteract: UIColor! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.cursorColorOnInteract) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.cursorColorOnInteract, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The size of the cursor in meters.
|
||||||
|
var cursorSize: Float! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.cursorSize) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.cursorSize, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to show the cursor overlay on the mesh surface.
|
||||||
|
var showCursor: Bool! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.showCursor) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.showCursor, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the user is actively interacting with the scene (e.g. click, tap, pinch, or drag).
|
||||||
|
var isInteracting: Bool! {
|
||||||
|
get { shaderGraphMaterial.getParameter(.isInteracting) }
|
||||||
|
set { try! shaderGraphMaterial.setParameter(.isInteracting, value: newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: CurvedUIMaterial, rhs: CurvedUIMaterial) -> Bool {
|
||||||
|
return lhs.gameTexture == rhs.gameTexture
|
||||||
|
&& lhs.cursorColor == rhs.cursorColor
|
||||||
|
&& lhs.cursorColorOnInteract == rhs.cursorColorOnInteract
|
||||||
|
&& lhs.cursorSize == rhs.cursorSize
|
||||||
|
&& lhs.showCursor == rhs.showCursor
|
||||||
|
&& lhs.isInteracting == rhs.isInteracting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private extension MaterialParameters.Handle {
|
||||||
|
static let gameTexture = ShaderGraphMaterial.parameterHandle(name: "GameTexture")
|
||||||
|
static let cursorColor = ShaderGraphMaterial.parameterHandle(name: "CursorColor")
|
||||||
|
static let cursorColorOnInteract = ShaderGraphMaterial.parameterHandle(name: "CursorColorOnInteract")
|
||||||
|
static let cursorSize = ShaderGraphMaterial.parameterHandle(name: "CursorSize")
|
||||||
|
static let showCursor = ShaderGraphMaterial.parameterHandle(name: "ShowCursor")
|
||||||
|
static let isInteracting = ShaderGraphMaterial.parameterHandle(name: "IsInteracting")
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ShaderGraphMaterial {
|
||||||
|
/// Convenience function to recover a typed shader parameter (without going through `MaterialParametres.Value` enum)
|
||||||
|
func getParameter<T>(_ handle: MaterialParameters.Handle, type: T.Type = T.self) -> T? {
|
||||||
|
guard let value = self.getParameter(handle: handle) else { return nil }
|
||||||
|
|
||||||
|
switch (type.self, value) {
|
||||||
|
case (is MaterialParameters.Texture.Type, .texture(let v)): return (v as! T)
|
||||||
|
case (is TextureResource.Type, .texture(let v)): return (v.resource as! T)
|
||||||
|
case (is TextureResource.Type, .textureResource(let v)): return (v as! T)
|
||||||
|
case (is Float.Type, .float(let v)): return (v as! T)
|
||||||
|
case (is SIMD2<Float>.Type, .simd2Float(let v)): return (v as! T)
|
||||||
|
case (is SIMD3<Float>.Type, .simd3Float(let v)): return (v as! T)
|
||||||
|
case (is SIMD4<Float>.Type, .simd4Float(let v)): return (v as! T)
|
||||||
|
case (is UIColor.Type, .color(let v)): fallthrough
|
||||||
|
case (is CGColor.Type, .color(let v)):
|
||||||
|
// `is CGColor` works for both UIColor and CGColor
|
||||||
|
if type == CGColor.self {
|
||||||
|
return (v as! T)
|
||||||
|
} else if type == UIColor.self {
|
||||||
|
return (UIColor(cgColor: v) as! T)
|
||||||
|
} else {
|
||||||
|
preconditionFailure("Unknown Color type \(type)")
|
||||||
|
}
|
||||||
|
case (is float2x2.Type, .float2x2(let v)): return (v as! T)
|
||||||
|
case (is float3x3.Type, .float3x3(let v)): return (v as! T)
|
||||||
|
case (is float4x4.Type, .float4x4(let v)): return (v as! T)
|
||||||
|
case (is Bool.Type, .bool(let v)): return (v as! T)
|
||||||
|
case (is Int.Type, .int(let v)): return (Int(v) as! T)
|
||||||
|
case (is Int32.Type, .int(let v)): return (v as! T)
|
||||||
|
default:
|
||||||
|
preconditionFailure("Invalid type \(type) for handle with value \(value)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function to set a typed shader parameter (without going through `MaterialParametres.Value` enum)
|
||||||
|
mutating func setParameter<T>(_ handle: MaterialParameters.Handle, value: T!) throws {
|
||||||
|
guard let value else { preconditionFailure("can not clear a material parameter") }
|
||||||
|
switch type(of: value).self {
|
||||||
|
case is MaterialParameters.Texture.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .texture(value as! MaterialParameters.Texture))
|
||||||
|
case is TextureResource.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .textureResource(value as! TextureResource))
|
||||||
|
case is Float.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .float(value as! Float))
|
||||||
|
case is SIMD2<Float>.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .simd2Float(value as! SIMD2<Float>))
|
||||||
|
case is SIMD3<Float>.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .simd3Float(value as! SIMD3<Float>))
|
||||||
|
case is SIMD4<Float>.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .simd4Float(value as! SIMD4<Float>))
|
||||||
|
case is CGColor.Type: fallthrough
|
||||||
|
case is UIColor.Type:
|
||||||
|
// `is CGColor` works for both UIColor and CGColor
|
||||||
|
if T.self == UIColor.self {
|
||||||
|
try self.setParameter(handle: handle, value: .color(value as! UIColor))
|
||||||
|
} else if T.self == CGColor.self {
|
||||||
|
try self.setParameter(handle: handle, value: .color(value as! CGColor))
|
||||||
|
} else {
|
||||||
|
preconditionFailure("Unknown Color type \(type(of: value))")
|
||||||
|
}
|
||||||
|
case is float2x2.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .float2x2(value as! float2x2))
|
||||||
|
case is float3x3.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .float3x3(value as! float3x3))
|
||||||
|
case is float4x4.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .float4x4(value as! float4x4))
|
||||||
|
case is Bool.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .bool(value as! Bool))
|
||||||
|
case is Int.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .int(Int32(value as! Int)))
|
||||||
|
case is Int32.Type:
|
||||||
|
try self.setParameter(handle: handle, value: .int(value as! Int32))
|
||||||
|
default:
|
||||||
|
preconditionFailure("Invalid type \(type(of: value))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
396
src/video/uikit/SDL_RealityKitHelper.swift
Normal file
396
src/video/uikit/SDL_RealityKitHelper.swift
Normal file
|
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
import RealityKit
|
||||||
|
import SwiftUI
|
||||||
|
import Metal
|
||||||
|
import MetalKit
|
||||||
|
import simd
|
||||||
|
|
||||||
|
/// Custom vertex format for the curved plane mesh.
|
||||||
|
/// Matches the layout described to LowLevelMesh via vertexAttributes/vertexLayouts.
|
||||||
|
private struct CurvedPlaneVertex {
|
||||||
|
var position: SIMD3<Float> = .zero
|
||||||
|
var normal: SIMD3<Float> = .zero
|
||||||
|
var uv: SIMD2<Float> = .zero
|
||||||
|
|
||||||
|
static var vertexAttributes: [LowLevelMesh.Attribute] {
|
||||||
|
[
|
||||||
|
.init(semantic: .position, format: .float3, offset: MemoryLayout<Self>.offset(of: \.position)!),
|
||||||
|
.init(semantic: .normal, format: .float3, offset: MemoryLayout<Self>.offset(of: \.normal)!),
|
||||||
|
.init(semantic: .uv0, format: .float2, offset: MemoryLayout<Self>.offset(of: \.uv)!)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
static var vertexLayouts: [LowLevelMesh.Layout] {
|
||||||
|
[.init(bufferIndex: 0, bufferStride: MemoryLayout<Self>.stride)]
|
||||||
|
}
|
||||||
|
|
||||||
|
static func descriptor(vertexCount: Int, indexCount: Int) -> LowLevelMesh.Descriptor {
|
||||||
|
var desc = LowLevelMesh.Descriptor()
|
||||||
|
desc.vertexAttributes = vertexAttributes
|
||||||
|
desc.vertexLayouts = vertexLayouts
|
||||||
|
desc.vertexCapacity = vertexCount
|
||||||
|
desc.indexCapacity = indexCount
|
||||||
|
desc.indexType = .uint32
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides RealityKit functionality
|
||||||
|
///
|
||||||
|
/// Key responsibilities:
|
||||||
|
/// - Generate curved mesh geometry procedurally using LowLevelMesh for fast updates
|
||||||
|
/// - Update textures using LowLevelTexture for efficient Metal → RealityKit transfer
|
||||||
|
/// - Asynchronously cooks a physics collision mesh of the curved UI to be used as an input target
|
||||||
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
internal class SDL_RealityKitHelper {
|
||||||
|
/// A collision shape which should be assigned to the same entity as ``lowLevelMesh``, for input targeting.
|
||||||
|
private(set) var collisionShape: ShapeResource? = nil
|
||||||
|
|
||||||
|
/// The TextureResource object which should be assigned to an entity in the scene.
|
||||||
|
private(set) var textureResource: TextureResource? = nil
|
||||||
|
|
||||||
|
/// The LowLevelMesh object which should be assigned to an entity in the scene, positioned at the origin.
|
||||||
|
///
|
||||||
|
/// This mesh is auomatically updated when you change ``meshGeometry`` via ``updateMeshGeometry()``.
|
||||||
|
/// LowLevelMesh is a class (reference type) so you can add it to your Entity's `MeshResource` once at init time.
|
||||||
|
let lowLevelMesh: LowLevelMesh
|
||||||
|
|
||||||
|
/// Topology characteristics of the generated mesh. This is fixed at initialization time.
|
||||||
|
let meshTopology: CurvedMeshTopology
|
||||||
|
|
||||||
|
/// The current generated mesh geometry. Update this with ``updateMeshGeometry()``
|
||||||
|
private(set) var meshGeometry: CurvedMeshGeometry = CurvedMeshGeometry(width: 1, height: 1)
|
||||||
|
|
||||||
|
/// An async task responsible for managing physics mesh cooking.
|
||||||
|
///
|
||||||
|
/// This guarantees that at most one cooking operation is active at a time.
|
||||||
|
/// Cooking generally takes > 1 frame, so it's important that there is not an explosion of redundant work
|
||||||
|
/// if there is a burst of resize activity.
|
||||||
|
private var physicsCookingTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
/// ``collisionShape`` is up to date with this `CurvedMeshGeometry`.
|
||||||
|
private var lastCookedGeometry: CurvedMeshGeometry?
|
||||||
|
|
||||||
|
/// LowLevelTexture that backs ``textureResource``.
|
||||||
|
private var lowLevelTexture: LowLevelTexture?
|
||||||
|
|
||||||
|
struct CurvedMeshTopology: Sendable, Equatable {
|
||||||
|
/// Number of horizontal segments to use to generate the mesh grid
|
||||||
|
var segmentsX: Int = 32
|
||||||
|
|
||||||
|
/// Number of vertical segments to use to generate the mesh grid
|
||||||
|
var segmentsY: Int = 32
|
||||||
|
|
||||||
|
/// Total number of vertices required to generate a mesh with this topology
|
||||||
|
var vertexCount: Int { (segmentsX + 1) * (segmentsY + 1) }
|
||||||
|
|
||||||
|
/// Total size of the index buffer when generating a mesh with this topology
|
||||||
|
var indexCount: Int { segmentsX * segmentsY * 6 }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CurvedMeshGeometry: Sendable, Equatable {
|
||||||
|
/// Width of the mesh in meters.
|
||||||
|
var width: Float
|
||||||
|
|
||||||
|
/// Height of the mesh in meters.
|
||||||
|
var height: Float
|
||||||
|
|
||||||
|
/// Radius of the mesh curvature in meters, or `nil` for a flat mesh.
|
||||||
|
var curvatureRadius: Float = 0
|
||||||
|
|
||||||
|
/// The bounding box of the mesh
|
||||||
|
var bounds: BoundingBox = BoundingBox()
|
||||||
|
|
||||||
|
/// Current snapped status
|
||||||
|
var snapped: Bool = false
|
||||||
|
|
||||||
|
/// Converts a 3D position on the mesh surface (in meters, relative to mesh center)
|
||||||
|
/// to normalized texture coordinates (0..1, 0..1).
|
||||||
|
func normalizedUV(fromMeshPosition position: SIMD3<Float>) -> SIMD2<Float> {
|
||||||
|
if curvatureRadius > 0 {
|
||||||
|
let halfWidth = bounds.extents.x / 2
|
||||||
|
|
||||||
|
let theta = asinf(halfWidth / curvatureRadius)
|
||||||
|
let angle = asinf(position.x / curvatureRadius)
|
||||||
|
|
||||||
|
let u = (angle / theta + 1) / 2
|
||||||
|
let v = (position.y / height) + 0.5
|
||||||
|
return SIMD2(u, v)
|
||||||
|
} else {
|
||||||
|
let u = (position.x / width) + 0.5
|
||||||
|
let v = (position.y / height) + 0.5
|
||||||
|
return SIMD2(u, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(meshTopology: CurvedMeshTopology = CurvedMeshTopology(),
|
||||||
|
meshGeometry: CurvedMeshGeometry = CurvedMeshGeometry(width: 1, height: 1)) {
|
||||||
|
self.meshTopology = meshTopology
|
||||||
|
self.meshGeometry = CurvedMeshGeometry(width: -1, height: -1)
|
||||||
|
|
||||||
|
let lowLevelMesh = try! meshTopology.generateMesh()
|
||||||
|
|
||||||
|
self.lowLevelMesh = lowLevelMesh
|
||||||
|
|
||||||
|
updateMeshGeometry(meshGeometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Mesh Generation (LowLevelMesh)
|
||||||
|
|
||||||
|
func updateSnappedStatus(snapped: Bool) {
|
||||||
|
var geometry = self.meshGeometry
|
||||||
|
geometry.snapped = snapped
|
||||||
|
updateMeshGeometry(geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMeshSize(width: Float, height: Float) {
|
||||||
|
var geometry = self.meshGeometry
|
||||||
|
geometry.width = width
|
||||||
|
geometry.height = height
|
||||||
|
updateMeshGeometry(geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMeshCurvature(curvatureRadius: Float) {
|
||||||
|
var geometry = self.meshGeometry
|
||||||
|
geometry.curvatureRadius = curvatureRadius
|
||||||
|
updateMeshGeometry(geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes vertex position/normal/uv data into the LowLevelMesh buffer.
|
||||||
|
/// This is the fast path — called on every size or curvature change without
|
||||||
|
/// recreating MeshResource or Entity.
|
||||||
|
func updateMeshGeometry(_ meshGeometry: CurvedMeshGeometry) {
|
||||||
|
if meshGeometry == self.meshGeometry {
|
||||||
|
return // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = meshGeometry.width
|
||||||
|
let height = meshGeometry.height
|
||||||
|
let curvatureRadius = meshGeometry.curvatureRadius
|
||||||
|
|
||||||
|
let segmentsX = meshTopology.segmentsX
|
||||||
|
let segmentsY = meshTopology.segmentsY
|
||||||
|
let indexCount = meshTopology.indexCount
|
||||||
|
|
||||||
|
var boundsMin = SIMD3(repeating: Float.infinity)
|
||||||
|
var boundsMax = SIMD3(repeating: -Float.infinity)
|
||||||
|
|
||||||
|
lowLevelMesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
|
||||||
|
let vertices = rawBytes.bindMemory(to: CurvedPlaneVertex.self)
|
||||||
|
|
||||||
|
if curvatureRadius > 0 {
|
||||||
|
|
||||||
|
// Apply cylindrical curve: Z varies with X to create wrap-around
|
||||||
|
var curve_positions: [SIMD3<Float>] = []
|
||||||
|
var curve_normals: [SIMD3<Float>] = []
|
||||||
|
let r = curvatureRadius
|
||||||
|
let arc_length = width / r
|
||||||
|
for x in 0...segmentsX {
|
||||||
|
let u = Float(x) / Float(segmentsX)
|
||||||
|
let angle = (u - 0.5) * arc_length
|
||||||
|
let vec: SIMD3<Float> = simd_normalize([sin(angle), 0.0, cos(angle)])
|
||||||
|
let pos: SIMD3<Float> = [vec.x, vec.y, 1.0 - vec.z] * r
|
||||||
|
curve_positions.append(pos)
|
||||||
|
|
||||||
|
// Normal points toward viewer for convex curve
|
||||||
|
curve_normals.append(-vec)
|
||||||
|
}
|
||||||
|
let offsetZ = meshGeometry.snapped ? 0 : -curve_positions[0].z
|
||||||
|
|
||||||
|
for y in 0...segmentsY {
|
||||||
|
let v = Float(y) / Float(segmentsY) * 2 - 1
|
||||||
|
let posY = v * height / 2
|
||||||
|
|
||||||
|
for x in 0...segmentsX {
|
||||||
|
let u = Float(x) / Float(segmentsX) * 2 - 1
|
||||||
|
|
||||||
|
let position = curve_positions[x] + SIMD3<Float>(0, posY, offsetZ)
|
||||||
|
let normal = curve_normals[x]
|
||||||
|
|
||||||
|
let idx = y * (segmentsX + 1) + x
|
||||||
|
vertices[idx].position = position
|
||||||
|
vertices[idx].normal = normal
|
||||||
|
vertices[idx].uv = SIMD2<Float>((u + 1) / 2, (v + 1) / 2)
|
||||||
|
|
||||||
|
boundsMin = min(boundsMin, position)
|
||||||
|
boundsMax = max(boundsMax, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Flat plane — same grid, z=0
|
||||||
|
for y in 0...segmentsY {
|
||||||
|
let v = Float(y) / Float(segmentsY)
|
||||||
|
let posY = (v - 0.5) * height
|
||||||
|
|
||||||
|
for x in 0...segmentsX {
|
||||||
|
let u = Float(x) / Float(segmentsX)
|
||||||
|
let posX = (u - 0.5) * width
|
||||||
|
|
||||||
|
let idx = y * (segmentsX + 1) + x
|
||||||
|
let position = SIMD3<Float>(posX, posY, 0)
|
||||||
|
vertices[idx].position = position
|
||||||
|
vertices[idx].normal = SIMD3<Float>(0, 0, -1)
|
||||||
|
vertices[idx].uv = SIMD2<Float>(u, v)
|
||||||
|
|
||||||
|
boundsMin = min(boundsMin, position)
|
||||||
|
boundsMax = max(boundsMax, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = BoundingBox(min: boundsMin, max: boundsMax)
|
||||||
|
lowLevelMesh.parts.replaceAll([
|
||||||
|
LowLevelMesh.Part(indexCount: indexCount, topology: .triangle, bounds: bounds)
|
||||||
|
])
|
||||||
|
|
||||||
|
self.meshGeometry = meshGeometry
|
||||||
|
self.meshGeometry.bounds = bounds
|
||||||
|
invalidatePhysicsMesh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Physics Mesh Cooking
|
||||||
|
|
||||||
|
/// Schedules an async physics mesh cook. If a cook is already in progress,
|
||||||
|
/// it will automatically re-cook when done if the geometry has changed.
|
||||||
|
private func invalidatePhysicsMesh() {
|
||||||
|
guard physicsCookingTask == nil else { return }
|
||||||
|
physicsCookingTask = Task {
|
||||||
|
defer { physicsCookingTask = nil }
|
||||||
|
// Loop until the cooked physics mesh matches the current geometry.
|
||||||
|
// Each iteration cooks against whatever the MeshResource currently reflects.
|
||||||
|
while lastCookedGeometry != meshGeometry {
|
||||||
|
let geometryAtStart = meshGeometry
|
||||||
|
do {
|
||||||
|
let meshResource = try await MeshResource(from: lowLevelMesh)
|
||||||
|
let shape = try await ShapeResource.generateStaticMesh(from: meshResource)
|
||||||
|
collisionShape = shape
|
||||||
|
lastCookedGeometry = geometryAtStart
|
||||||
|
} catch {
|
||||||
|
NSLog("SDL_RealityKitHelper: Failed to generate physics mesh: %@", error.localizedDescription)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Texture Updates (LowLevelTexture Pipeline)
|
||||||
|
|
||||||
|
/// Creates or recreates the LowLevelTexture for the given dimensions
|
||||||
|
private func ensureLowLevelTexture(width: Int, height: Int, pixelFormat: MTLPixelFormat) {
|
||||||
|
// Check if we need to recreate (size or format changed)
|
||||||
|
if let lowLevelTexture,
|
||||||
|
lowLevelTexture.descriptor.width == width,
|
||||||
|
lowLevelTexture.descriptor.height == height,
|
||||||
|
lowLevelTexture.descriptor.pixelFormat == pixelFormat
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//NSLog("SDL_RealityKitHelper: Creating LowLevelTexture %dx%d", width, height)
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Create LowLevelTexture descriptor using Metal pixel format directly
|
||||||
|
var descriptor = LowLevelTexture.Descriptor()
|
||||||
|
descriptor.textureType = .type2D
|
||||||
|
descriptor.pixelFormat = pixelFormat
|
||||||
|
descriptor.width = width
|
||||||
|
descriptor.height = height
|
||||||
|
descriptor.depth = 1
|
||||||
|
let size = max(width, height)
|
||||||
|
if (size > 32) {
|
||||||
|
descriptor.mipmapLevelCount = Int(floor(log2(Float(size)))) - 5
|
||||||
|
} else {
|
||||||
|
descriptor.mipmapLevelCount = 0
|
||||||
|
}
|
||||||
|
descriptor.textureUsage = [.shaderRead, .renderTarget]
|
||||||
|
|
||||||
|
// Create the LowLevelTexture
|
||||||
|
lowLevelTexture = try LowLevelTexture(descriptor: descriptor)
|
||||||
|
|
||||||
|
// Create TextureResource from LowLevelTexture (this is reusable)
|
||||||
|
textureResource = try TextureResource(from: lowLevelTexture!)
|
||||||
|
|
||||||
|
//NSLog("SDL_RealityKitHelper: LowLevelTexture created successfully")
|
||||||
|
} catch {
|
||||||
|
NSLog("SDL_RealityKitHelper: ERROR - Failed to create LowLevelTexture: %@", error.localizedDescription)
|
||||||
|
lowLevelTexture = nil
|
||||||
|
textureResource = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func getDisplayTexture(_ commandBuffer: MTLCommandBuffer, width: Int, height: Int, pixelFormat: MTLPixelFormat) -> MTLTexture? {
|
||||||
|
// Ensure LowLevelTexture exists with correct dimensions
|
||||||
|
ensureLowLevelTexture(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
pixelFormat: pixelFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let llt = lowLevelTexture else {
|
||||||
|
NSLog("SDL_RealityKitHelper: ERROR - No LowLevelTexture available")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the writable texture from LowLevelTexture
|
||||||
|
return llt.replace(using: commandBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SDL_RealityKitHelper.CurvedMeshTopology {
|
||||||
|
@MainActor
|
||||||
|
func generateMesh() throws -> LowLevelMesh {
|
||||||
|
//NSLog("SDL_RealityKitHelper: Creating LowLevelMesh (%dx%d grid, %d vertices, %d indices)",
|
||||||
|
// segmentsX, segmentsY, vertexCount, indexCount)
|
||||||
|
|
||||||
|
// Create LowLevelMesh with our custom vertex format
|
||||||
|
let desc = CurvedPlaneVertex.descriptor(vertexCount: vertexCount, indexCount: indexCount)
|
||||||
|
let mesh = try LowLevelMesh(descriptor: desc)
|
||||||
|
|
||||||
|
// Write index buffer once — topology never changes for a fixed grid
|
||||||
|
mesh.withUnsafeMutableIndices { rawIndices in
|
||||||
|
let indices = rawIndices.bindMemory(to: UInt32.self)
|
||||||
|
var idx = 0
|
||||||
|
for y in 0..<segmentsY {
|
||||||
|
for x in 0..<segmentsX {
|
||||||
|
let i0 = UInt32(y * (segmentsX + 1) + x)
|
||||||
|
let i1 = i0 + 1
|
||||||
|
let i2 = i0 + UInt32(segmentsX + 1)
|
||||||
|
let i3 = i2 + 1
|
||||||
|
|
||||||
|
// Two triangles per quad (counter-clockwise winding)
|
||||||
|
indices[idx] = i0
|
||||||
|
indices[idx + 1] = i1
|
||||||
|
indices[idx + 2] = i2
|
||||||
|
indices[idx + 3] = i1
|
||||||
|
indices[idx + 4] = i3
|
||||||
|
indices[idx + 5] = i2
|
||||||
|
idx += 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/video/uikit/SDL_UIKitBridge-objc.h
Normal file
50
src/video/uikit/SDL_UIKitBridge-objc.h
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
#ifndef SDL_uikitvisionosscene_h_
|
||||||
|
#define SDL_uikitvisionosscene_h_
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <Metal/Metal.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the curved content pointer mode is enabled
|
||||||
|
*/
|
||||||
|
bool SDL_VisionOS_PointerModeEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any window is using curved content mode (UIHostingController-based).
|
||||||
|
*/
|
||||||
|
bool SDL_UIKit_HasCurvedWindow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a window is using curved content mode (UIHostingController-based).
|
||||||
|
*
|
||||||
|
* @param window The SDL window to check.
|
||||||
|
* @return true if the window is in curved mode, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SDL_UIKit_IsCurvedWindow(SDL_Window *window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the curved content display texture.
|
||||||
|
*/
|
||||||
|
id<MTLTexture> SDL_UIKit_GetCurvedDisplayTexture(SDL_Window *window, id<MTLCommandBuffer> commandBuffer, int width, int height, MTLPixelFormat pixelFormat);
|
||||||
|
|
||||||
|
#endif /* SDL_uikitvisionosscene_h_ */
|
||||||
39
src/video/uikit/SDL_UIKitBridge-swift.h
Normal file
39
src/video/uikit/SDL_UIKitBridge-swift.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
#import "SDL_uikitviewcontroller.h"
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when window size changes
|
||||||
|
void SDL_VisionOS_SendSizeChanged(long width, long height);
|
||||||
|
|
||||||
|
// Called from Swift scene delegates to get the initial curvature
|
||||||
|
float SDL_VisionOS_GetCurvature();
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when window curvature changes
|
||||||
|
void SDL_VisionOS_SendCurvatureChanged(float curvature);
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when pointer mode changes
|
||||||
|
void SDL_VisionOS_SendPointerMode(bool enabled);
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when visionOS delivers a touch event
|
||||||
|
void SDL_VisionOS_SendTouch(NSTimeInterval timestamp, SDL_FingerID fingerID, Uint32 eventType, float x, float y);
|
||||||
|
|
||||||
|
// Called from Swift to register the RealityKit hosting object with the SDL window
|
||||||
|
void SDL_VisionOS_SetWindowRealityKitHosting(id hosting);
|
||||||
187
src/video/uikit/SDL_UIKitBridge.m
Normal file
187
src/video/uikit/SDL_UIKitBridge.m
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
#include "SDL_internal.h"
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
|
||||||
|
#include "SDL_UIKitBridge-objc.h"
|
||||||
|
#include "SDL_UIKitBridge-swift.h"
|
||||||
|
#include "SDL_uikitevents.h"
|
||||||
|
#include "SDL_uikitwindow.h"
|
||||||
|
#include "SDL_uikitmetalview.h"
|
||||||
|
#include "../../events/SDL_events_c.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when window size changes
|
||||||
|
void SDL_VisionOS_SendSizeChanged(long width, long height)
|
||||||
|
{
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (window) {
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
CGRect bounds = CGRectMake(0, 0, width, height);
|
||||||
|
|
||||||
|
// Update the UIWindow
|
||||||
|
data.uiwindow.frame = bounds;
|
||||||
|
|
||||||
|
// Update the view
|
||||||
|
UIView *view = data.viewcontroller.view;
|
||||||
|
view.bounds = bounds;
|
||||||
|
|
||||||
|
// Update the metal layer
|
||||||
|
if ([view isKindOfClass:[SDL_uikitmetalview class]]) {
|
||||||
|
SDL_uikitmetalview *metalview = (SDL_uikitmetalview *)view;
|
||||||
|
|
||||||
|
[metalview updateDrawableSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the resize event
|
||||||
|
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, (int)width, (int)height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from Swift scene delegates to get the initial curvature
|
||||||
|
float SDL_VisionOS_GetCurvature()
|
||||||
|
{
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (window) {
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
return data.curvature;
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when window curvature changes
|
||||||
|
void SDL_VisionOS_SendCurvatureChanged(float curvature)
|
||||||
|
{
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (window) {
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
if (curvature != data.curvature) {
|
||||||
|
data.curvature = curvature;
|
||||||
|
SDL_SetFloatProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_CURVATURE_FLOAT, curvature);
|
||||||
|
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_CURVATURE_CHANGED, (int)curvature, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SDL_pointer_mode;
|
||||||
|
|
||||||
|
void SDL_VisionOS_SendPointerMode(bool enabled)
|
||||||
|
{
|
||||||
|
SDL_pointer_mode = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_VisionOS_PointerModeEnabled()
|
||||||
|
{
|
||||||
|
return SDL_pointer_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from Swift scene delegates when visionOS delivers a touch event
|
||||||
|
void SDL_VisionOS_SendTouch(NSTimeInterval timestamp, SDL_FingerID fingerID, Uint32 eventType, float x, float y)
|
||||||
|
{
|
||||||
|
const SDL_TouchID directTouchId = 1;
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float pressure;
|
||||||
|
if (eventType == SDL_EVENT_FINGER_DOWN || eventType == SDL_EVENT_FINGER_MOTION) {
|
||||||
|
pressure = 1.0f;
|
||||||
|
} else {
|
||||||
|
pressure = 0.0f;
|
||||||
|
}
|
||||||
|
if (eventType == SDL_EVENT_FINGER_MOTION) {
|
||||||
|
SDL_SendTouchMotion(UIKit_GetEventTimestamp(timestamp), directTouchId, fingerID, window, x, y, pressure);
|
||||||
|
} else {
|
||||||
|
SDL_SendTouch(UIKit_GetEventTimestamp(timestamp), directTouchId, fingerID, window, (SDL_EventType)eventType, x, y, pressure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - RealityKit Content Hosting
|
||||||
|
|
||||||
|
// Called from Swift to register the RealityKit hosting object with the SDL window.
|
||||||
|
void SDL_VisionOS_SetWindowRealityKitHosting(id hosting)
|
||||||
|
{
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (!window) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "VISIONOS: No focused window for RealityKit hosting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UIKitWindowData *windowData = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
windowData.curvedContentHosting = hosting;
|
||||||
|
|
||||||
|
// Updating curvedContentHosting updates the view controller so that the "container background" is hidden.
|
||||||
|
// On visionOS, this gets rid of the default glass background effect (not wanted for our content).
|
||||||
|
[windowData.viewcontroller setNeedsUpdateOfPreferredContainerBackgroundStyle];
|
||||||
|
|
||||||
|
//SDL_Log("VISIONOS: RealityKit hosting registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_UIKit_HasCurvedWindow()
|
||||||
|
{
|
||||||
|
SDL_Window *window = SDL_GetToplevelForKeyboardFocus();
|
||||||
|
if (window) {
|
||||||
|
return SDL_UIKit_IsCurvedWindow(window);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_UIKit_IsCurvedWindow(SDL_Window *window)
|
||||||
|
{
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
return data && data.curvedContentHosting;
|
||||||
|
}
|
||||||
|
|
||||||
|
id<MTLTexture> SDL_UIKit_GetCurvedDisplayTexture(SDL_Window *window, id<MTLCommandBuffer> commandBuffer, int width, int height, MTLPixelFormat pixelFormat)
|
||||||
|
{
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
|
||||||
|
if (!data || !data.curvedContentHosting) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
id hosting = data.curvedContentHosting;
|
||||||
|
SEL getTextureSelector = NSSelectorFromString(@"getDisplayTexture:width:height:pixelFormat:");
|
||||||
|
if (![hosting respondsToSelector:getTextureSelector]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMethodSignature *signature = [hosting methodSignatureForSelector:getTextureSelector];
|
||||||
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||||
|
[invocation setSelector:getTextureSelector];
|
||||||
|
[invocation setTarget:hosting];
|
||||||
|
|
||||||
|
long arg_width = width;
|
||||||
|
long arg_height = height;
|
||||||
|
[invocation setArgument:&commandBuffer atIndex:2];
|
||||||
|
[invocation setArgument:&arg_width atIndex:3];
|
||||||
|
[invocation setArgument:&arg_height atIndex:4];
|
||||||
|
[invocation setArgument:&pixelFormat atIndex:5];
|
||||||
|
[invocation invoke];
|
||||||
|
|
||||||
|
__unsafe_unretained id temp = nil;
|
||||||
|
[invocation getReturnValue:&temp];
|
||||||
|
id<MTLTexture> texture = temp;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* SDL_PLATFORM_VISIONOS */
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include "SDL_uikitopengles.h"
|
#include "SDL_uikitopengles.h"
|
||||||
#include "SDL_uikitvideo.h"
|
#include "SDL_uikitvideo.h"
|
||||||
#include "SDL_uikitwindow.h"
|
#include "SDL_uikitwindow.h"
|
||||||
|
#include "SDL_UIKitBridge-objc.h"
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <GameController/GameController.h>
|
#import <GameController/GameController.h>
|
||||||
|
|
@ -308,6 +309,12 @@ static bool SetGCMouseRelativeMode(bool enabled)
|
||||||
static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
|
static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
|
||||||
{
|
{
|
||||||
Uint64 timestamp = SDL_GetTicksNS();
|
Uint64 timestamp = SDL_GetTicksNS();
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
if (!SDL_VisionOS_PointerModeEnabled() && SDL_UIKit_HasCurvedWindow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, pressed);
|
SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,19 +325,19 @@ static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14
|
||||||
SDL_AddMouse(mouseID, NULL);
|
SDL_AddMouse(mouseID, NULL);
|
||||||
|
|
||||||
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||||
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
|
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
|
||||||
};
|
};
|
||||||
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||||
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
|
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
|
||||||
};
|
};
|
||||||
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||||
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed);
|
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed);
|
||||||
};
|
};
|
||||||
|
|
||||||
int auxiliary_button = SDL_BUTTON_X1;
|
int auxiliary_button = SDL_BUTTON_X1;
|
||||||
for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) {
|
for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) {
|
||||||
btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||||
OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
|
OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
|
||||||
};
|
};
|
||||||
++auxiliary_button;
|
++auxiliary_button;
|
||||||
}
|
}
|
||||||
|
|
@ -338,21 +345,32 @@ static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14
|
||||||
mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouseInput, float deltaX, float deltaY) {
|
mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouseInput, float deltaX, float deltaY) {
|
||||||
Uint64 timestamp = SDL_GetTicksNS();
|
Uint64 timestamp = SDL_GetTicksNS();
|
||||||
|
|
||||||
if (SDL_GCMouseRelativeMode()) {
|
bool send_motion = SDL_GCMouseRelativeMode();
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
if (!send_motion && SDL_VisionOS_PointerModeEnabled()) {
|
||||||
|
send_motion = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (send_motion) {
|
||||||
SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, true, deltaX, -deltaY);
|
SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, true, deltaX, -deltaY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
|
mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
|
||||||
Uint64 timestamp = SDL_GetTicksNS();
|
Uint64 timestamp = SDL_GetTicksNS();
|
||||||
|
|
||||||
/* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis.
|
/* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis.
|
||||||
* The vertical values are negative moving the mouse wheel up and positive moving it down.
|
* The vertical values are negative moving the mouse wheel up and positive moving it down.
|
||||||
* The horizontal values are negative moving the mouse wheel left and positive moving it right.
|
* The horizontal values are negative moving the mouse wheel left and positive moving it right.
|
||||||
* The vertical values are inverted compared to SDL, and the horizontal values are as expected.
|
* The vertical values are inverted compared to SDL, and the horizontal values are as expected.
|
||||||
*/
|
*/
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
float vertical = -yValue;
|
||||||
|
float horizontal = xValue;
|
||||||
|
#else
|
||||||
float vertical = -xValue;
|
float vertical = -xValue;
|
||||||
float horizontal = yValue;
|
float horizontal = yValue;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) {
|
if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) {
|
||||||
// Since these are raw values, we need to flip them ourselves
|
// Since these are raw values, we need to flip them ourselves
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
arising from the use of this software.
|
arising from the use of this software.
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
including commercial applications, and to alter it and redistribute it
|
including commercial applications, and to alter it and redistribute it
|
||||||
freely, subject to the following restrictions:
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
* @author Mark Callow, www.edgewise-consulting.com.
|
* @author Mark Callow, www.edgewise-consulting.com.
|
||||||
*
|
*
|
||||||
|
|
@ -43,6 +42,8 @@
|
||||||
- (instancetype)initWithFrame:(CGRect)frame
|
- (instancetype)initWithFrame:(CGRect)frame
|
||||||
scale:(CGFloat)scale;
|
scale:(CGFloat)scale;
|
||||||
|
|
||||||
|
- (void)updateDrawableSize;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window);
|
SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window);
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
arising from the use of this software.
|
arising from the use of this software.
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
including commercial applications, and to alter it and redistribute it
|
including commercial applications, and to alter it and redistribute it
|
||||||
freely, subject to the following restrictions:
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
* @author Mark Callow, www.edgewise-consulting.com.
|
* @author Mark Callow, www.edgewise-consulting.com.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
arising from the use of this software.
|
arising from the use of this software.
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
including commercial applications, and to alter it and redistribute it
|
including commercial applications, and to alter it and redistribute it
|
||||||
freely, subject to the following restrictions:
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
claim that you wrote the original software. If you use this software
|
claim that you wrote the original software. If you use this software
|
||||||
in a product, an acknowledgment in the product documentation would be
|
in a product, an acknowledgment in the product documentation would be
|
||||||
appreciated but is not required.
|
appreciated but is not required.
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
misrepresented as being the original software.
|
misrepresented as being the original software.
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
*/
|
*/
|
||||||
#include "SDL_internal.h"
|
#include "SDL_internal.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@
|
||||||
- (void)loadView;
|
- (void)loadView;
|
||||||
- (void)viewDidLayoutSubviews;
|
- (void)viewDidLayoutSubviews;
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
- (void)initializeVisionOSCurvedUI;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef SDL_PLATFORM_TVOS
|
#ifndef SDL_PLATFORM_TVOS
|
||||||
- (NSUInteger)supportedInterfaceOrientations;
|
- (NSUInteger)supportedInterfaceOrientations;
|
||||||
- (BOOL)prefersStatusBarHidden;
|
- (BOOL)prefersStatusBarHidden;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@
|
||||||
#include "SDL_uikitwindow.h"
|
#include "SDL_uikitwindow.h"
|
||||||
#include "SDL_uikitopengles.h"
|
#include "SDL_uikitopengles.h"
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
#import "SDL3/SDL3-Swift.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SDL_PLATFORM_TVOS
|
#ifdef SDL_PLATFORM_TVOS
|
||||||
static void SDLCALL SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
static void SDLCALL SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||||
{
|
{
|
||||||
|
|
@ -119,6 +123,15 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
if (@available(visionOS 26.0, *)) {
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)self.window->internal;
|
||||||
|
if (data.curvature >= 0.0f) {
|
||||||
|
[self initializeVisionOSCurvedUI];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,6 +154,19 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
- (UIContainerBackgroundStyle)preferredContainerBackgroundStyle
|
||||||
|
{
|
||||||
|
if (self.window) {
|
||||||
|
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)self.window->internal;
|
||||||
|
if (data && data.curvedContentHosting) {
|
||||||
|
return UIContainerBackgroundStyleHidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UIContainerBackgroundStyleAutomatic;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
||||||
{
|
{
|
||||||
SDL_SetSystemTheme(UIKit_GetSystemTheme());
|
SDL_SetSystemTheme(UIKit_GetSystemTheme());
|
||||||
|
|
|
||||||
33
src/video/uikit/SDL_uikitviewcontroller.swift
Normal file
33
src/video/uikit/SDL_uikitviewcontroller.swift
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SDL_uikitviewcontroller {
|
||||||
|
@available(visionOS 26.0, *)
|
||||||
|
@objc func initializeVisionOSCurvedUI() {
|
||||||
|
Task {
|
||||||
|
let hosting = SDL_CurvedContentHosting()
|
||||||
|
hosting.present(from: self)
|
||||||
|
SDL_VisionOS_SetWindowRealityKitHosting(hosting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -52,6 +52,12 @@ extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window);
|
||||||
// Array of SDL_uikitviews owned by this window.
|
// Array of SDL_uikitviews owned by this window.
|
||||||
@property(nonatomic, copy) NSMutableArray *views;
|
@property(nonatomic, copy) NSMutableArray *views;
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
// Hosting controller for curved content mode (UIHostingController-based)
|
||||||
|
@property(nonatomic, strong) id curvedContentHosting;
|
||||||
|
@property(nonatomic, assign) CGFloat curvature;
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#endif // SDL_uikitwindow_h_
|
#endif // SDL_uikitwindow_h_
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow *uiwindow, bool created)
|
static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow *uiwindow, SDL_PropertiesID create_props, bool created)
|
||||||
{
|
{
|
||||||
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
|
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
|
||||||
SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
|
SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
|
||||||
|
|
@ -106,6 +106,19 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
|
||||||
#endif
|
#endif
|
||||||
window->w = width;
|
window->w = width;
|
||||||
window->h = height;
|
window->h = height;
|
||||||
|
|
||||||
|
SDL_PropertiesID props = SDL_GetWindowProperties(window);
|
||||||
|
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow);
|
||||||
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
|
||||||
|
|
||||||
|
#ifdef SDL_PLATFORM_VISIONOS
|
||||||
|
float curvature = SDL_GetFloatProperty(create_props, SDL_PROP_WINDOW_CREATE_CURVATURE_FLOAT, -1.0f);
|
||||||
|
if (curvature > 0.0f && curvature <= 1.0f) {
|
||||||
|
curvature = 0.0f;
|
||||||
|
}
|
||||||
|
data.curvature = curvature;
|
||||||
|
SDL_SetFloatProperty(props, SDL_PROP_WINDOW_CURVATURE_FLOAT, curvature);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* The View Controller will handle rotating the view when the device
|
/* The View Controller will handle rotating the view when the device
|
||||||
* orientation changes. This will trigger resize events, if appropriate. */
|
* orientation changes. This will trigger resize events, if appropriate. */
|
||||||
|
|
@ -119,10 +132,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
|
||||||
* hierarchy. */
|
* hierarchy. */
|
||||||
[view setSDLWindow:window];
|
[view setSDLWindow:window];
|
||||||
|
|
||||||
SDL_PropertiesID props = SDL_GetWindowProperties(window);
|
|
||||||
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow);
|
|
||||||
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +237,7 @@ bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!SetupWindowData(_this, window, uiwindow, true)) {
|
if (!SetupWindowData(_this, window, uiwindow, create_props, true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue