mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-05-13 23:34:16 +02:00
Added curved window mode on visionOS 26 (#15298)
This commit is contained in:
@@ -50,13 +50,14 @@
|
||||
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 */; };
|
||||
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, ); };
|
||||
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, ); };
|
||||
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, ); }; };
|
||||
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, ); }; };
|
||||
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 */; };
|
||||
@@ -82,8 +83,8 @@
|
||||
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 */; };
|
||||
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, ); }; };
|
||||
A7381E971D8B6A0300B177DD /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E931D8B69C300B177DD /* AudioToolbox.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); };
|
||||
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, xros, ); };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -434,6 +433,13 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -559,7 +565,7 @@
|
||||
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 */; };
|
||||
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 */
|
||||
|
||||
/* 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; };
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -970,7 +977,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1026,6 +1032,13 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1149,6 +1162,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1738,8 +1752,15 @@
|
||||
A7D8A61823E2513D00DCD162 /* uikit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3A8956D2F7D8AAA00B9E5C2 /* SDL_CurvedContentHosting.swift */,
|
||||
F3A8956E2F7D8AAA00B9E5C2 /* SDL_CurvedContentView.swift */,
|
||||
3AFD09E92F9766BA00208BA9 /* SDL_CurvedUIShader.swift */,
|
||||
F3A837162F69C80100AD32B6 /* SDL_RealityKitHelper.swift */,
|
||||
A7D8A62F23E2513D00DCD162 /* SDL_uikitappdelegate.h */,
|
||||
A7D8A61E23E2513D00DCD162 /* SDL_uikitappdelegate.m */,
|
||||
F3A895762F7DC14400B9E5C2 /* SDL_UIKitBridge.m */,
|
||||
F3A895772F7DC14400B9E5C2 /* SDL_UIKitBridge-objc.h */,
|
||||
F3A895782F7DC14400B9E5C2 /* SDL_UIKitBridge-swift.h */,
|
||||
A7D8A62123E2513D00DCD162 /* SDL_uikitclipboard.h */,
|
||||
A7D8A62A23E2513D00DCD162 /* SDL_uikitclipboard.m */,
|
||||
A7D8A62D23E2513D00DCD162 /* SDL_uikitevents.h */,
|
||||
@@ -1754,18 +1775,19 @@
|
||||
A7D8A62323E2513D00DCD162 /* SDL_uikitopengles.m */,
|
||||
A7D8A62B23E2513D00DCD162 /* SDL_uikitopenglview.h */,
|
||||
A7D8A62023E2513D00DCD162 /* SDL_uikitopenglview.m */,
|
||||
000063D3D80F97ADC7770000 /* SDL_uikitpen.h */,
|
||||
000053D344416737F6050000 /* SDL_uikitpen.m */,
|
||||
A7D8A62223E2513D00DCD162 /* SDL_uikitvideo.h */,
|
||||
A7D8A63223E2513D00DCD162 /* SDL_uikitvideo.m */,
|
||||
A7D8A61923E2513D00DCD162 /* SDL_uikitview.h */,
|
||||
A7D8A62923E2513D00DCD162 /* SDL_uikitview.m */,
|
||||
A7D8A62423E2513D00DCD162 /* SDL_uikitviewcontroller.h */,
|
||||
A7D8A63023E2513D00DCD162 /* SDL_uikitviewcontroller.m */,
|
||||
F3A8957C2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift */,
|
||||
A7D8A63323E2513D00DCD162 /* SDL_uikitvulkan.h */,
|
||||
A7D8A62523E2513D00DCD162 /* SDL_uikitvulkan.m */,
|
||||
A7D8A62723E2513D00DCD162 /* SDL_uikitwindow.h */,
|
||||
A7D8A61A23E2513D00DCD162 /* SDL_uikitwindow.m */,
|
||||
000063D3D80F97ADC7770000 /* SDL_uikitpen.h */,
|
||||
000053D344416737F6050000 /* SDL_uikitpen.m */,
|
||||
);
|
||||
path = uikit;
|
||||
sourceTree = "<group>";
|
||||
@@ -2365,17 +2387,6 @@
|
||||
path = vulkan;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2388,6 +2399,17 @@
|
||||
path = gpu;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2561,6 +2583,8 @@
|
||||
F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */,
|
||||
F3EFA5EE2D5AB97300BCF22F /* stb_image.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 */,
|
||||
A7D8AF0623E2514100DCD162 /* SDL_cocoamessagebox.h in Headers */,
|
||||
A7D8AEB223E2514100DCD162 /* SDL_cocoametalview.h in Headers */,
|
||||
@@ -2843,6 +2867,9 @@
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1130;
|
||||
TargetAttributes = {
|
||||
BECDF5FE0761BA81005FE872 = {
|
||||
LastSwiftMigration = 2630;
|
||||
};
|
||||
F3676F582A7885080091160D = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
@@ -2931,6 +2958,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A7D8B9E323E2514400DCD162 /* SDL_drawline.c in Sources */,
|
||||
F3A8957B2F7DC14400B9E5C2 /* SDL_UIKitBridge.m in Sources */,
|
||||
A7D8AE7C23E2514100DCD162 /* SDL_yuv.c in Sources */,
|
||||
A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */,
|
||||
A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */,
|
||||
@@ -3059,6 +3087,7 @@
|
||||
F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */,
|
||||
A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */,
|
||||
A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */,
|
||||
F3A8957D2F7DC15500B9E5C2 /* SDL_uikitviewcontroller.swift in Sources */,
|
||||
A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */,
|
||||
89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */,
|
||||
A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */,
|
||||
@@ -3086,6 +3115,7 @@
|
||||
A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */,
|
||||
A7D8B4DC23E2514300DCD162 /* SDL_joystick.c in Sources */,
|
||||
A7D8BA4923E2514400DCD162 /* SDL_render_gles2.c in Sources */,
|
||||
F3A8371C2F69C80100AD32B6 /* SDL_RealityKitHelper.swift in Sources */,
|
||||
A7D8AC2D23E2514100DCD162 /* SDL_surface.c in Sources */,
|
||||
A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */,
|
||||
A7D8AD2323E2514100DCD162 /* SDL_blit_auto.c in Sources */,
|
||||
@@ -3141,6 +3171,8 @@
|
||||
A7D8A94B23E2514000DCD162 /* SDL.c in Sources */,
|
||||
A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m 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 */,
|
||||
63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */,
|
||||
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
|
||||
@@ -3153,6 +3185,7 @@
|
||||
00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
|
||||
000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */,
|
||||
F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */,
|
||||
3AFD09EA2F9766BA00208BA9 /* SDL_CurvedUIShader.swift in Sources */,
|
||||
F3FBB10A2DDF93AB0000F9A0 /* SDL_hidapi_gamesir.c in Sources */,
|
||||
0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */,
|
||||
0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */,
|
||||
@@ -3239,13 +3272,18 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_LINK_OBJC_RUNTIME = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||
OTHER_LDFLAGS = "-liconv";
|
||||
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;
|
||||
};
|
||||
@@ -3305,13 +3343,19 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_LINK_OBJC_RUNTIME = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../../src/dynapi/SDL_dynapi.exports";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
|
||||
OTHER_LDFLAGS = "-liconv";
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -163,8 +163,9 @@ typedef enum SDL_EventType
|
||||
associated with the window. Otherwise, the handle has already been destroyed and all resources
|
||||
associated with it are invalid */
|
||||
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_LAST = SDL_EVENT_WINDOW_HDR_STATE_CHANGED,
|
||||
SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_CURVATURE_CHANGED,
|
||||
|
||||
/* Keyboard events */
|
||||
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
|
||||
* 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
|
||||
* not add a graphics API specific property
|
||||
* (`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_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_CURVATURE_FLOAT "SDL.window.create.curvature"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
* \returns a valid property ID on success or 0 on failure; call
|
||||
* 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_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_CURVATURE_FLOAT "SDL.window.curvature"
|
||||
|
||||
/**
|
||||
* 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_DESTROYED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED);
|
||||
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_CURVATURE_CHANGED);
|
||||
#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)
|
||||
|
||||
@@ -177,7 +177,12 @@ bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat forma
|
||||
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SDL_RenderingLinearSpace(SDL_Renderer *renderer)
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#endif
|
||||
#ifdef SDL_VIDEO_DRIVER_UIKIT
|
||||
#import <UIKit/UIKit.h>
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
#import "../../video/uikit/SDL_UIKitBridge-objc.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 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 *allpipelines;
|
||||
@property(nonatomic, assign) int pipelinescount;
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
@property(nonatomic, retain) id<MTLTexture> mtlrealitykittexture;
|
||||
#endif
|
||||
@end
|
||||
|
||||
@implementation SDL3METAL_RenderData
|
||||
@@ -453,16 +459,25 @@ static bool METAL_ActivateRenderCommandEncoder(SDL_Renderer *renderer, MTLLoadAc
|
||||
SDL3METAL_TextureData *texdata = (__bridge SDL3METAL_TextureData *)renderer->target->internal;
|
||||
mtltexture = texdata.mtltexture;
|
||||
} else {
|
||||
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;
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
if (renderer->window && SDL_UIKit_IsCurvedWindow(renderer->window)) {
|
||||
data.mtlrealitykittexture = SDL_UIKit_GetCurvedDisplayTexture(renderer->window, [data.mtlcmdqueue commandBuffer], (int)data.mtllayer.drawableSize.width, (int)data.mtllayer.drawableSize.height, data.mtllayer.pixelFormat);
|
||||
mtltexture = data.mtlrealitykittexture;
|
||||
} else
|
||||
#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)
|
||||
{
|
||||
@autoreleasepool {
|
||||
SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
|
||||
id<MTLTexture> mtltexture;
|
||||
MTLRegion mtlregion;
|
||||
SDL_Rect read_rect = *rect;
|
||||
Uint32 format;
|
||||
SDL_Surface *surface;
|
||||
|
||||
@@ -1951,6 +2011,15 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||
}
|
||||
#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
|
||||
* sure the GPU has finished rendering to it by the time we read it. */
|
||||
[data.mtlcmdbuffer commit];
|
||||
@@ -1958,7 +2027,7 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||
data.mtlcmdencoder = 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) {
|
||||
case MTLPixelFormatBGRA8Unorm:
|
||||
@@ -1991,9 +2060,16 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
|
||||
SDL_SetError("Unknown framebuffer pixel format");
|
||||
return NULL;
|
||||
}
|
||||
surface = SDL_CreateSurface(rect->w, rect->h, format);
|
||||
surface = SDL_CreateSurface(read_rect.w, read_rect.h, format);
|
||||
if (surface) {
|
||||
[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;
|
||||
}
|
||||
@@ -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.
|
||||
// But we'll still try to commit the command buffer in case it was already enqueued.
|
||||
if (ready) {
|
||||
SDL_assert(data.mtlbackbuffer != nil);
|
||||
[data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
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];
|
||||
@@ -2057,6 +2147,11 @@ static void METAL_DestroyRenderer(SDL_Renderer *renderer)
|
||||
[data.mtlcmdencoder endEncoding];
|
||||
}
|
||||
|
||||
if (data.mtlcmdbuffer != nil) {
|
||||
[data.mtlcmdbuffer commit];
|
||||
[data.mtlcmdbuffer waitUntilCompleted];
|
||||
}
|
||||
|
||||
DestroyAllPipelines(data.allpipelines, data.pipelinescount);
|
||||
|
||||
/* 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_uikitvideo.h"
|
||||
#include "SDL_uikitwindow.h"
|
||||
#include "SDL_UIKitBridge-objc.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <GameController/GameController.h>
|
||||
@@ -308,6 +309,12 @@ static bool SetGCMouseRelativeMode(bool enabled)
|
||||
static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -318,19 +325,19 @@ static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14
|
||||
SDL_AddMouse(mouseID, NULL);
|
||||
|
||||
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) {
|
||||
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
|
||||
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, 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;
|
||||
for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) {
|
||||
btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
|
||||
OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
|
||||
};
|
||||
++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) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
|
||||
/* 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 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.
|
||||
*/
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
float vertical = -yValue;
|
||||
float horizontal = xValue;
|
||||
#else
|
||||
float vertical = -xValue;
|
||||
float horizontal = yValue;
|
||||
#endif
|
||||
|
||||
if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) {
|
||||
// Since these are raw values, we need to flip them ourselves
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
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.
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
/*
|
||||
* @author Mark Callow, www.edgewise-consulting.com.
|
||||
*
|
||||
@@ -43,6 +42,8 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
scale:(CGFloat)scale;
|
||||
|
||||
- (void)updateDrawableSize;
|
||||
|
||||
@end
|
||||
|
||||
SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window);
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
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.
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
/*
|
||||
* @author Mark Callow, www.edgewise-consulting.com.
|
||||
*
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
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.
|
||||
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:
|
||||
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.
|
||||
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"
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
- (void)loadView;
|
||||
- (void)viewDidLayoutSubviews;
|
||||
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
- (void)initializeVisionOSCurvedUI;
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_TVOS
|
||||
- (NSUInteger)supportedInterfaceOrientations;
|
||||
- (BOOL)prefersStatusBarHidden;
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
#include "SDL_uikitwindow.h"
|
||||
#include "SDL_uikitopengles.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_VISIONOS
|
||||
#import "SDL3/SDL3-Swift.h"
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_TVOS
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -141,6 +154,19 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
|
||||
#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
|
||||
{
|
||||
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.
|
||||
@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
|
||||
|
||||
#endif // SDL_uikitwindow_h_
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
@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_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
|
||||
@@ -106,6 +106,19 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
|
||||
#endif
|
||||
window->w = width;
|
||||
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
|
||||
* orientation changes. This will trigger resize events, if appropriate. */
|
||||
@@ -119,10 +132,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
|
||||
* hierarchy. */
|
||||
[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;
|
||||
}
|
||||
|
||||
@@ -228,7 +237,7 @@ bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!SetupWindowData(_this, window, uiwindow, true)) {
|
||||
if (!SetupWindowData(_this, window, uiwindow, create_props, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user