bkaradzic / bgfx

Cross-platform, graphics API agnostic, "Bring Your Own Engine/Framework" style rendering library.
https://bkaradzic.github.io/bgfx/overview.html
BSD 2-Clause "Simplified" License
15.07k stars 1.94k forks source link

Support Building for Mac Catalyst (`x86_64h-apple-ios13.x-macabi`) #2064

Open DylanLukes opened 4 years ago

DylanLukes commented 4 years ago

I'm opening this as an issue rather than a PR because it's:

1) Not finalized. 2) Affects bx (namely, scripts/toolchain.lua), but also requires some changes to bgfx.

All in all, building for Mac Catalyst was not too hard. It is essentially "just like" building for iOS, except:

1) The required link and build options are a little different. 2) OpenGL ES does not exist and must be conditioned out.

I haven't thoroughly tested this yet, but at the least it should provide a starting point.

diff --git a/scripts/toolchain.lua b/scripts/toolchain.lua
index 08e2302..8e411ad 100644
--- a/scripts/toolchain.lua
+++ b/scripts/toolchain.lua
@@ -71,6 +71,7 @@ function toolchain(_buildDir, _libDir)
            { "mingw-clang",     "MinGW (clang compiler)"     },
            { "netbsd",          "NetBSD"                     },
            { "osx",             "OSX"                        },
+           { "mac-catalyst",    "Mac Catalyst"               },
            { "orbis",           "Orbis"                      },
            { "riscv",           "RISC-V"                     },
            { "rpi",             "RaspberryPi"                },
@@ -103,6 +104,7 @@ function toolchain(_buildDir, _libDir)
        description = "Choose XCode target",
        allowed = {
            { "osx", "OSX" },
+           { "mac-catalyst", "Mac Catalyst"},
            { "ios", "iOS" },
            { "tvos", "tvOS" },
        }
@@ -385,6 +387,14 @@ function toolchain(_buildDir, _libDir)
            end
            location (path.join(_buildDir, "projects", _ACTION .. "-osx"))

+       elseif "mac-catalyst" == _OPTIONS["gcc"] then
+           if os.is("linux") then
+               print("Mac Catalyst is not supported on Linux.")
+               os.exit(1)
+           end
+
+           location (path.join(_buildDir, "projects", _ACTION .. "-mac-catalyst"))
+
        elseif "orbis" == _OPTIONS["gcc"] then

            if not os.getenv("SCE_ORBIS_SDK_DIR") then
@@ -485,6 +495,11 @@ function toolchain(_buildDir, _libDir)
            premake.xcode.toolset = "macosx"
            location (path.join(_buildDir, "projects", _ACTION .. "-osx"))

+       elseif "mac-catalyst" == _OPTIONS["xcode"] then
+           action.xcode.macOSTargetPlatformVersion = str_or(macosPlatform, "10.15")
+           premake.xcode.toolset = "macosx"
+           location (path.join(_buildDir, "projects", _ACTION .. "-mac-catalyst"))
+       
        elseif "ios" == _OPTIONS["xcode"] then
            action.xcode.iOSTargetPlatformVersion = str_or(iosPlatform, "8.0")
            premake.xcode.toolset = "iphoneos"
@@ -1046,6 +1061,28 @@ function toolchain(_buildDir, _libDir)
        }
        includedirs { path.join(bxDir, "include/compat/osx") }

+   configuration { "mac-catalyst" }
+       targetdir (path.join(_buildDir, "mac_catalyst/bin"))
+       objdir (path.join(_buildDir, "mac_catalyst/obj"))
+       linkoptions {
+           "-DTARGET_OS_MACCATALYST=1",
+           "-target x86_64h-apple-ios" .. (#iosPlatform > 0 and iosPlatform or "13.0") .. "-macabi",
+           "-iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/iOSSupport/System/Library/Frameworks",
+           "-isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/iOSSupport/usr/include",
+           "-lc++",
+       }
+       buildoptions {
+           "-DTARGET_OS_MACCATALYST=1",   -- Does xcrun/xcode set this normally? Clang isn't...
+           "-Wfatal-errors",
+           "-msse2",
+           "-Wunused-value",
+           "-Wundef",
+           "-target x86_64h-apple-ios" .. (#iosPlatform > 0 and iosPlatform or "13.0") .. "-macabi",
+           "-iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/iOSSupport/System/Library/Frameworks",
+           "-isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/iOSSupport/usr/include"
+       }
+       includedirs { path.join(bxDir, "include/compat/osx") }
+
    configuration { "ios*" }
        linkoptions {
            "-lc++",

As for bgfx itself...

diff --git a/makefile b/makefile
index b5c8be372..b9d1834ff 100644
--- a/makefile
+++ b/makefile
@@ -169,6 +169,14 @@ osx-release64: .build/projects/gmake-osx ## Build - OSX x64 Release
    $(MAKE) -C .build/projects/gmake-osx config=release64
 osx: osx-debug64 osx-release64 ## Build - OSX x64 Debug and Release

+.build/projects/gmake-mac-catalyst:
+   $(GENIE) --gcc=mac-catalyst gmake
+mac-catalyst-debug64: .build/projects/gmake-mac-catalyst ## Build - Mac Catalyst x64 Debug
+   $(MAKE) -C .build/projects/gmake-mac-catalyst config=debug64
+mac-catalyst-release64: .build/projects/gmake-mac-catalyst ## Build - Mac Catalyst x64 Release
+   $(MAKE) -C .build/projects/gmake-mac-catalyst config=release64
+mac-catalyst: mac-catalyst-debug64 mac-catalyst-release64 ## Build - Mac Catalyst x64 Debug and Release
+
 .build/projects/gmake-ios-arm:
    $(GENIE) --gcc=ios-arm gmake
 ios-arm-debug: .build/projects/gmake-ios-arm ## Build - iOS ARM Debug
diff --git a/src/config.h b/src/config.h
index 3c0ee614b..a13769dcd 100644
--- a/src/config.h
+++ b/src/config.h
@@ -82,13 +82,13 @@
 #  endif // BGFX_CONFIG_RENDERER_OPENGLES_MIN_VERSION

 #  ifndef BGFX_CONFIG_RENDERER_OPENGLES
-#      define BGFX_CONFIG_RENDERER_OPENGLES (0 \
-                   || BX_PLATFORM_ANDROID      \
-                   || BX_PLATFORM_EMSCRIPTEN   \
-                   || BX_PLATFORM_IOS          \
-                   || BX_PLATFORM_RPI          \
-                   || BX_PLATFORM_STEAMLINK    \
-                   || BX_PLATFORM_NX           \
+#      define BGFX_CONFIG_RENDERER_OPENGLES (0                    \
+                   || BX_PLATFORM_ANDROID                         \
+                   || BX_PLATFORM_EMSCRIPTEN                      \
+                   || BX_PLATFORM_IOS && !TARGET_OS_MACCATALYST   \
+                   || BX_PLATFORM_RPI                             \
+                   || BX_PLATFORM_STEAMLINK                       \
+                   || BX_PLATFORM_NX                              \
                    ? BGFX_CONFIG_RENDERER_OPENGLES_MIN_VERSION : 0)
 #  endif // BGFX_CONFIG_RENDERER_OPENGLES

diff --git a/src/renderer_gl.h b/src/renderer_gl.h
index 2348970ce..b085edb6a 100644
--- a/src/renderer_gl.h
+++ b/src/renderer_gl.h
@@ -1066,7 +1066,7 @@ typedef uint64_t GLuint64;
 #  include "glcontext_glx.h"
 #elif BX_PLATFORM_OSX
 #  include "glcontext_nsgl.h"
-#elif BX_PLATFORM_IOS
+#elif BX_PLATFORM_IOS && !TARGET_OS_MACCATALYST
 #  include "glcontext_eagl.h"
 #endif // BX_PLATFORM_
bkaradzic commented 4 years ago

So what is Catalyst exactly?

OSX or iOS?

If it's OSX then it should be: { "osx-catalyst", "OSX Catalyst" },

Also if bx is identifying platform it should not use IOS defines to turn on off OSX features.

bkaradzic commented 4 years ago

Also you should not use TARGET_OS_* defines anywhere outside of platform.h, BX_PLATFORM_OSX could be defined in the way to identify that particular OS.

DylanLukes commented 4 years ago

Yeah, this is a very, very rough cut. It is definitely not ideal, just a first pass.

So... Catalyst is... both. Kind of.

Catalyst is for (roughly speaking) running iPad apps on macOS. In essence, it provides versions of the iOS specific APIs such as UIKit that work on macOS. It's the iOS runtime environment, but on macOS (with an appropriately distinct ABI). I originally defined it as osx-catalyst as you described. But it turned out this configuration shared more in common with the iOS configurations than it does with the OSX configurations.

TARGET_OS_MACCATALYST is what xcodebuild/xcrun/etc define, and what Apple suggests using for macroing in/out code that is MacOS specific in Catalyst enabled projects. I initially had a distinct BX_PLATFORM_MACCATALYST, but pruned it for simplicity. I think going back to that would be a cleaner approach.

Currently, the requirements are that you target iOS version 13+, and macOS version 10.15+. The architecture for Catalyst builds is x86_64h (Haswell) though in practice you often need the x86_64 slice in your fat binaries as well for development purposes.

One additional problem with the setup above is that it should ideally make use of both iosPlatform and osxPlatform, as in the future there will likely be valid combinations beyond 13.x/10.15.x.

A note: this API is explicitly marketed towards people as a way to (e.g.) develop a game for iPad and then easily make it available on macOS. It's seems at least somewhat likely at least some current iOS bgfx users may want support for it going forward.

bkaradzic commented 4 years ago

Ok then it's more like tvos with some extra compile time things. It could be ios-catalyst and then follow BX_PLATFORM_IOS defines.

DylanLukes commented 4 years ago

I'll clean things up once I've got some demos running and am sure it's fully functional.

How should I submit a PR when it's across two repos? Just two distinct PRs and xref them?

Also: it can't exactly follow BX_PLATFORM_IOS defines because Catalyst (like macOS 10.15) does not support OpenGL ES in any capacity.

It might also be good to have both x86_64 and x86_64h as options, or build and lipo them both together. Xcode will complain if you lack either (on my machine at least, on a Haswell Mac it won't care about the missing x86_64.)

bkaradzic commented 4 years ago

How should I submit a PR when it's across two repos? Just two distinct PRs and xref them?

First submit bx to get all functionality you need there.

iOS will stop supporting GLES anyway at some point, it's just checking version. Idea is to identify platform iOS because it's closest to that, but have some way of disabling GLES.

DylanLukes commented 4 years ago

Finding more than a few small hiccups as I go, I'll document them here for reference.

  1. Mac Catalyst's Metal does not support MTLStorageModeShared, only MTLStorageModeManaged and MTLStorageModePrivate. As a result, here:

https://github.com/bkaradzic/bgfx/blob/master/src/renderer_mtl.mm#L2721

Will error out on bgfx::init(...):

validateMTLStorageMode, line 237: error 'invalid storageMode (0). Must be one of MTLStorageModeManaged(1) MTLStorageModePrivate(2)'
DylanLukes commented 4 years ago

There's definitely some weirdness going on. For some reason, dbgTextPrintf() does not work... however TextVideoMem and blitting do work otherwise, as BGFX_DEBUG_STATS does produce output.

As far as I can tell these code paths are pretty similar, except for when the TVM is created, and I can confirm the TVM's memory looks correct. I'll try to put together a minimal failing test case.

bkaradzic commented 4 years ago

@attilaz would be the best to add this support, since he is very familiar with all Apple platforms and author of Metal renderer.

attilaz commented 4 years ago

I haven't tried catalyst, but after reading the docs from the metal renderer point of view catalyst supports the same features as macos. Because it runs on the same hardware. Windowing is similar to iOS with UI APIs instead of NS.

For example the metal gpu family is "MTLGPUFamilyMacCatalyst1" which is "A family 1 Mac GPU when running in an iOS app."

https://developer.apple.com/documentation/metal/mtlgpufamily/mtlgpufamilymaccatalyst1?language=objc

DylanLukes commented 4 years ago

I've been continuing to try to get things working, and have found there are a good number of (mostly subtle) differences.

I'm not entirely sure BX_PLATFORM_IOS would actually be appropriate. I'm finding myself having to write BX_PLATFORM_IOS & [!]TARGET_OS_MACCATALYST in a good few places.

The aforementioned storage mode issue is an example of this. However, the change I made to get it working on Mac (Catalyst) has made it no longer work correctly on iPad (when build targeting catalyst). One step forward, another step back...

I'm currently trying to set up some sort of harness to run the tests/examples, but glfw can't be built for Catalyst (doesn't expose the necessary windowing APIs), so I'm trying to see if I can build glfw just for x86_64h-apple-... while still being able to link to x86_64h-apple-ios13.x-macabi.