Metal support RC0 released

If you are not familiar with metal support, you can refer to beta0 release.

Compared to beta0 version, this version completes these work:

  • optimize Sprite creation speed
  • add some documentations
    • migration from v3 to v4 or using using v4 with cocopod can refer to this doc
    • new concept of v4 can refer to the docs
    • API changes doc is here
    • some examples can be found here
  • update luajit to latest 2.1
  • update GLFW to 3.3
  • remove tiff
  • Webview and VideoPlayer adapt to iOS 13
  • fix system font issue on macOS 15
  • fix memory leak on mac
  • many bugs fixed

Download

cocos2d-x4.0rc0

6 Likes

Just a minor documentation issue, api_change_v4 cocos2d::image, these 2 lines:

    Rename premultiplyAlpha to premultipliedAlpha.
    Remove reversePremultipliedAlpha

Should be:

    Rename premultipliedAlpha to premultiplyAlpha.
    Added reversePremultipliedAlpha

Since they were part of the change that allows developers to save images as non-PMA.

@zhangxm I’m having some difficulty updating the shader code to what is required in v4, either because of a bug in cocos2d-x or my misunderstanding of how to implement the changes.

I create different sprites, assign a new backend::ProgramState* to each one, and then set the required values into the shader uniform variables. The fragment shader changes the color of the texture, so it’s nothing particularly complex.

Now, say that I create 20 sprites in a loop, each with a different color sent to the uniform variable, then the issue is that all 20 sprites will have the same color as the last sprite created.

EDIT: If I set a ProgramState on every second sprite, then those sprites are colored correctly.

It looks like when every second sprite has the ProgramState attached, it is breaking sprite batching, which is why it causes the shader to actually work as intended for each of those sprites that have the ProgramState set on them.

So, is the problem in the Sprite batching code? For some reason it seems to be batching all the sprites that use the same backend::Program, but it’s ignoring the fact that different uniform values are being set for each of them. So, in this case, the last u_color set ends up as the color for all the sprites, when it shouldn’t be.

Expected (from Cocos2d-x v3.17.2):

Actual (from v4.0rc0):

Demo project that reproduces the problem:
gamename.zip (1.7 MB)

1 Like

@R101 we will take a look.

It doesn’t generate .xcodeproj file ((

@Zaga Do you read the document about how to run firstly? The project should be generated easily by cmake. As for your question, can you describe in detail about the steps you have done?

@coulsonwang Everything is ok, my mistake.

auto test

After i create the ios-build folder using CMake, if i try to move the whole cocos2d-x folder to another new location, the cocos2d-x folder keeps regenerating in the old location with some contents in it. Is this some CMake behaviour? And how can i stop it or make cmake build into the new folder location? Since i have already geenrated the project files, i know want to copy them to my v3 structure without using cmake again. In case i need to update cocos in future, i will repeate process with cmake.

I have a slight concern with part of the implementation of backend::Program and backend::ProgramState.

Please correct me if I’m wrong in my understanding of how the code works.

Right now when we create a Program, we can only do so by providing the string source to both the vertex and fragment shaders via this method:

backend::ProgramCache::getInstance()->newProgram(vertexShader, fragmentShader);

This newProgram() function does the following:

backend::Program* ProgramCache::newProgram(const std::string& vertexShader, const std::string& fragmentShader)
{
    auto key = std::hash<Shader>{}(Shader(vertexShader, fragmentShader));
    const auto& iter = ProgramCache::_cachedPrograms.find(key);
    if (ProgramCache::_cachedPrograms.end() != iter)
    {
        CC_SAFE_RETAIN(iter->second);
        return iter->second;
    }
    
    auto program = backend::Device::getInstance()->newProgram(vertexShader, fragmentShader);
    ProgramCache::_cachedPrograms.emplace(key, program);
    
    return program;
}

So, it creates a hash from the vertex and fragment shader strings, and uses that as the lookup key for the _cachedPrograms std::unordered_map.

Now, here are my concerns:

  1. There is no way for developers to check if a Program already exists, since there was no way to pass in a unique identifier.

  2. In order to check if a Program exists, we need to pass in the strings for the vertex and fragment shader into the “newProgram()” function. This results in 2 issues:

    a. We are required to reload the shader code each time, and while it may not be an issue if it’s always stored in RAM (although it means it’s just taking up memory for no reason),
    it may be an issue if we have to hit the internal device storage (using FileUtils etc) to load it every time we need to use it.

    b. The std::hash is computed over the entire code of each of the 2 shaders, so the larger the code for the shader, the longer this process takes (I’m assuming that’s how the std::hash works).

  3. There is no way to create a ProgramState from a Program directly, as the only way to create it is to pass in the source for both shaders:

ProgramState::ProgramState(const std::string& vertexShader, const std::string& fragmentShader)
{
    _program = backend::ProgramCache::getInstance()->newProgram(vertexShader, fragmentShader);
    CC_SAFE_RETAIN(_program);

    init();
}

This ends up forcing the loading of the shaders, then the hashing of the strings, then the key lookup, every single time we want to create a ProgramState from a Program that we know already exists in the ProgramCache.

If you can re-assure me that the performance impact of this way of creating the Program and ProgramState is negligible in all scenarios, then that’s fine. If testing hasn’t been done to measure the potential impact on performance because of the loading and hashing of the strings (remember there are some large and complex shaders out there), then perhaps it’s a good idea to do this testing before the final release of Cocos2d-x v4.

Personally, I wouldn’t be bothered if we passed in our own unique identifier to use as the lookup key for the Program, rather than using a hash for this purpose. Also, just an idea, there’s the option of using both a hash and a user defined key, stored in two lists:

std::unordered_map<string (user defined ID), Program*> _customIdProgramCache;
std::unordered_map<hash, Program*> _cachedPrograms;

That way we can call something like:

backend::Program* ProgramCache::newProgram(const std::string& programId, const std::string& vertexShader, const std::string& fragmentShader)
{
    const auto& iter1 = ProgramCache::_customIdProgramCache.find(programId);
    if (ProgramCache::_customIdProgramCache.end() != iter1)
    {
        CC_SAFE_RETAIN(iter1->second);
        return iter1->second;
    }
    
    auto key = std::hash<Shader>{}(Shader(vertexShader, fragmentShader));
    const auto& iter2 = ProgramCache::_cachedPrograms.find(key);
    if (ProgramCache::_cachedPrograms.end() != iter2)
    {
        ProgramCache::_customIdProgramCache.emplace(programId, iter2->second); // if it doesn't exist in the this map, then add it
        CC_SAFE_RETAIN(iter2->second);
        return iter2->second;
    }
    
    auto program = backend::Device::getInstance()->newProgram(vertexShader, fragmentShader);
    ProgramCache::_cachedPrograms.emplace(key, program);
    ProgramCache::_customIdProgramCache.emplace(programId, program);

    return program;
}

My reasoning here is that the majority of the time the developer will know exactly when they’ve created and added a new Program, and what ID belongs to it, but in certain situations a different ID may be used for exactly the same vertex and fragment shader combination, so keeping the standard hash lookup would prevent creating another instance of an equivalent Program.

Another alternative (which is probably simpler) is to just return the hash result, and add the functionality for us to be able to check if it exists, along with a way to create the ProgramState* from a Program*, something like this:

backend::Program* ProgramCache::newProgram(const std::string& vertexShader, const std::string& fragmentShader, std::size_t* programIdResult = nullptr)
{
    auto key = std::hash<Shader>{}(Shader(vertexShader, fragmentShader));
    if (programIdResult)
    {
        *programIdResult = key;
    }
    const auto& iter = ProgramCache::_cachedPrograms.find(key);
    if (ProgramCache::_cachedPrograms.end() != iter)
    {
        CC_SAFE_RETAIN(iter->second);

        return iter->second;
    }
    
    auto program = backend::Device::getInstance()->newProgram(vertexShader, fragmentShader);
    ProgramCache::_cachedPrograms.emplace(key, program);

    return program;
}

Anyhow, my primary concern is the potential impact of the current way of handling the shaders, so I would appreciate any feedback regarding those points.

I remember some talk of 3D support being discontinued in v4, but I’ve seen change logs suggesting it’s still supported. Is that the case?

It’s not discontinued.

1 Like

That’s great news about 3D. Thank you cocos2d-x developers for supporting this.

1 Like

@zhangxm Spine doesn’t support Metal

(

I’m attempting to use make to build the v4 project on macos but am having absolutely no luck, using CMAKE 3.15.4

For the mac project I get:

Determining if the CXX compiler works failed with the following output:
Change Dir: /Users/___/cocos2d-x/mac-build/CMakeFiles/CMakeTmp

Run Build Command:"/usr/bin/xcodebuild" "-project" "CMAKE_TRY_COMPILE.xcodeproj" "build" "-target" "cmTC_5a73d" "-configuration" "Debug"
note: Using new build system
2019-10-04 12:01:35.227 xcodebuild[64271:6314926] unable to create build operation: Error Domain=com.apple.xcbuild Code=1 "unknown error while handling message: missingTarget(guid: "9432d6d62b1c625ff09e3cb530813f40b82180076f82025385fc1d75dfab6aa2")" UserInfo={NSLocalizedDescription=unknown error while handling message: missingTarget(guid: "9432d6d62b1c625ff09e3cb530813f40b82180076f82025385fc1d75dfab6aa2")}

** BUILD FAILED **

And for iOS:

Compiling the CXX compiler identification source file "CMakeCXXCompilerId.cpp" failed.
Compiler:
Build flags:
Id flags:

The output was:
65
note: Using new build system
note: Planning build
note: Constructing build description
error: Signing for "CompilerIdCXX" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'CompilerIdCXX' from project 'CompilerIdCXX')
warning: Traditional headermap style is no longer supported; please migrate to using separate headermaps and set 'ALWAYS_SEARCH_USER_PATHS' to NO. (in target 'CompilerIdCXX' from project 'CompilerIdCXX')

** BUILD FAILED **

I see that the documentation says

if you want to sign iOS app in CMake, you will need to fill development team ID

But I don’t have any particular desire to sign with cmake and just want to generate the project.
Is there something I’m missing? My installed XCode version is 11.0 (11A419c)

I also think that R101 suggested good idea to have function which returns backend::Program* or null (or may be launch newProgram(…, …) if additional shader strings will be passed) directly via its hash.
The fact is that it is very convenient to keep shader in separate file, rather than in const char*, but frequent access to the file can take a long time.

When thinking about UIKit for Mac (Catalyst), I thought that prebuilds like luajit should be xcframework or source build.

@kerryk cmake will generate project file according to the folder structure set in CMakeLists.txt.
@R101 as i think, shader are always loaded at first, and shaders are not complex, so it is not a problem. But we will consider your concern and suggestion carefully.
@Glidos 3D is still supported, you can run the tests the check it.
@Zaga Spine is not supported right now, the author of Spine doesn’t want to merge pull request from us, and we can not modify the codes. We may update pull request again after releasing.
@Guykun you need macOS15 + Xcode 11 to support metal on simulator. You can create Xcode project first, then run on iOS devices.

1 Like

I’m personally using shaders that are approximately 400 lines in length, and roughly 12KB in size, so loading this from device storage and running std::hash on it every time it needs to be used is a little concerning.