summaryrefslogtreecommitdiff
path: root/platform/darwin/src/headless_backend_cgl.cpp
blob: a21bcad9d02a99d3de2eb26ee3cb980fdeaacee6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <mbgl/gl/headless_backend.hpp>
#include <mbgl/util/logging.hpp>

#include <OpenGL/OpenGL.h>
#include <CoreFoundation/CoreFoundation.h>

#include <string>
#include <stdexcept>

namespace mbgl {
namespace gl {

// This class provides a singleton that contains information about the pixel format used for
// instantiating new headless rendering contexts.
class CGLDisplayConfig {
private:
    // Key for singleton construction.
    struct Key { explicit Key() = default; };

public:
    CGLDisplayConfig(Key) {
        // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
        // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
        CGLPixelFormatAttribute attributes[] = {
            kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
            // Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when running in VMs.
            kCGLPFAClosestPolicy,
            kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
            kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24),
            kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8),
            kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(16),
            kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8),
            kCGLPFASupportsAutomaticGraphicsSwitching,
            kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
            static_cast<CGLPixelFormatAttribute>(0)
        };

        GLint num;
        // TODO: obtain all configurations and choose the best one.
        const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
        if (error != kCGLNoError) {
            throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + "\n");
        }
        if (num <= 0) {
            throw std::runtime_error("No pixel formats found.");
        }
    }

    ~CGLDisplayConfig() {
        const CGLError error = CGLDestroyPixelFormat(pixelFormat);
        if (error != kCGLNoError) {
            Log::Error(Event::OpenGL, std::string("Error destroying pixel format:") + CGLErrorString(error));
        }
    }

    static std::shared_ptr<const CGLDisplayConfig> create() {
        static std::weak_ptr<const CGLDisplayConfig> instance;
        auto shared = instance.lock();
        if (!shared) {
            instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
        }
        return shared;
    }

public:
    CGLPixelFormatObj pixelFormat = nullptr;
};

class CGLBackendImpl : public HeadlessBackend::Impl {
public:
    CGLBackendImpl() {
        CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
        if (error != kCGLNoError) {
            throw std::runtime_error(std::string("Error creating GL context object:") +
                                     CGLErrorString(error) + "\n");
        }

        error = CGLEnable(glContext, kCGLCEMPEngine);
        if (error != kCGLNoError) {
            throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
                                     CGLErrorString(error) + "\n");
        }
    }

    ~CGLBackendImpl() final {
        CGLDestroyContext(glContext);
    }

    gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
        static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
        if (!framework) {
            throw std::runtime_error("Failed to load OpenGL framework.");
        }

        CFStringRef str =
            CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
        void* symbol = CFBundleGetFunctionPointerForName(framework, str);
        CFRelease(str);

        return reinterpret_cast<gl::ProcAddress>(symbol);
    }

    void activateContext() final {
        CGLError error = CGLSetCurrentContext(glContext);
        if (error != kCGLNoError) {
            throw std::runtime_error(std::string("Switching OpenGL context failed:") +
                                     CGLErrorString(error) + "\n");
        }
    }

    void deactivateContext() final {
        CGLError error = CGLSetCurrentContext(nullptr);
        if (error != kCGLNoError) {
            throw std::runtime_error(std::string("Removing OpenGL context failed:") +
                                     CGLErrorString(error) + "\n");
        }
    }

private:
    const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
    CGLContextObj glContext = nullptr;
};

void HeadlessBackend::createImpl() {
    assert(!impl);
    impl = std::make_unique<CGLBackendImpl>();
}

} // namespace gl
} // namespace mbgl