SDL icon indicating copy to clipboard operation
SDL copied to clipboard

Force SDL to use ANGLE when OpenGL ES Hint is Used (and Statically Linking ANGLE)

Open time-killer-games opened this issue 2 years ago • 2 comments

So, this code works:

https://github.com/time-killer-games/ween

... when dynamically linking ANGLE and statically linking SDL2. Though I need both statically linked, as this is being required, by a client I am doing this for, and am being paid to do. Plus, I myself prefer one file anyway and not several. However though I managed to static link it ok, the code from the previous repository no longer works once I am linking statically to both SDL2 and ANGLE. I am attempting to do this on macOS because I'd like to not depend on macOS's OpenGL framework anymore if and when Apple kills off their official support for OpenGL that is provided OS-level. All I need ANGLE for is getting the GPU Vendor and Renderer Strings. I am creating a context. Relevant (C++) code from repository (bare-minimum reproducible) ...

system.cpp

#include <sstream>
#include <vector>
#include <string>

#include <SDL.h>
#if (defined(__APPLE__) && defined(__MACH__))
#include <SDL_opengles2.h>
#else
#include <SDL_opengl.h>
#endif

/* Define CREATE_CONTEXT in your build scripts or Makefiles if
the calling process hasn't already done this on its own ... */
#if defined(CREATE_CONTEXT)
static SDL_Window *window = nullptr;
static bool CreateContext() {
  if (!window) {
    #if (defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__sun))
    setenv("SDL_VIDEODRIVER", "x11", 1);
    SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
    #endif
    if (SDL_Init(SDL_INIT_VIDEO)) return false;
    #if (defined(__APPLE__) && defined(__MACH__))
    // TODO: Find a way to get this working when statically linking SDL2 and ANGLE; it only works with dynamic ANGLE ...
    // glGetString(...) will return nullptr whenever the ES context creation failed; a context is required for ANGLE ...
    SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    #endif
    window = SDL_CreateWindow("", 0, 0, 1, 1, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
    if (!window) return false;
    SDL_GLContext context = SDL_GL_CreateContext(window);
    if (!context) return false;
    int err = SDL_GL_MakeCurrent(window, context);
    if (err) return false;
  }
  return true;
}
#endif

static std::string GpuVendor;
std::string GetGpuVendor() {
  if (!GpuVendor.empty()) return GpuVendor;
  #if defined(CREATE_CONTEXT)
  if (!CreateContext()) return "";
  #endif
  #if (defined(__APPLE__) && defined(__MACH__))
  PFNGLGETSTRINGPROC glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString");
  const char *result = (char *)glGetStringFunc(GL_VENDOR);
  #else
  // Also tried just this on macOS, still returns nullptr with static ANGLE:
  const char *result = (char *)glGetString(GL_VENDOR);
  #endif
  std::string str;
  str = result ? result : "";
  #if (defined(__APPLE__) && defined(__MACH__))
  // Trim out of string useless info showing I used Google's ANGLE libraries ...
  std::size_t openp = str.find_first_of("(");
  std::size_t closep = str.find_last_of(")");
  if (openp != std::string::npos && closep != std::string::npos) {
    str = str.substr(0, closep);
    str = str.substr(openp + 1);
  }
  #endif
  GpuVendor = str;
  return str;
}

static std::string GpuRenderer;
std::string GetGpuRenderer() {
  if (!GpuRenderer.empty()) return GpuRenderer;
  #if defined(CREATE_CONTEXT)
  if (!CreateContext()) return "";
  #endif
  #if (defined(__APPLE__) && defined(__MACH__))
  PFNGLGETSTRINGPROC glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString");
  const char *result = (char *)glGetStringFunc(GL_RENDERER);
  #else
  // Also tried just this on macOS, still returns nullptr with static ANGLE:
  const char *result = (char *)glGetString(GL_RENDERER);
  #endif
  std::string str;
  str = result ? result : "";
  #if (defined(__APPLE__) && defined(__MACH__))
  // Trim out of string useless info showing I used Google's ANGLE libraries ...
  std::size_t openp = str.find_first_of("(");
  std::size_t closep = str.find_last_of(")");
  if (openp != std::string::npos && closep != std::string::npos) {
    str = str.substr(0, closep);
    str = str.substr(openp + 1);
    std::vector<std::string> vec;
    std::stringstream sstr(str);
    std::string tmp;
    while (std::getline(sstr, tmp, ',')) {
      vec.push_back(tmp);
    }
    if (vec.size() >= 2) {
      str = vec[1];
      if (str.length() >= 1 && str[0] == ' ') {
        str = str.substr(1);
      }
    }
  }
  #endif
  GpuRenderer = str;
  return str;
}

system.hpp:

#include <string>

std::string GetGpuVendor();
std::string GetGpuRenderer();

main.cpp

#include <iostream>

#include "system.hpp"

#if defined(_WIN32)
#undef main
#endif

int main() {
  std::cout <<
  "GPU Vendor:   " << GetGpuVendor() << "\n" <<
  "GPU Renderer: " << GetGpuRenderer() << "\n";
  return 0;
}

build.sh:

#!/bin/sh
cd "${0%/*}"
if [ $(uname) = "Darwin" ]; then
  sudo port install libsdl2 +universal python311 ninja && git clone https://chromium.googlesource.com/angle/angle && git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git && sudo port select --set python python311 && export PATH=`pwd`/depot_tools:"$PATH" && cd angle && python scripts/bootstrap.py && gclient sync && git checkout main;
  mkdir ../arm64 && gn gen ../arm64 && echo "target_cpu = \"arm64\"" >> ../arm64/args.gn && echo "angle_enable_metal=true" >> ../arm64/args.gn && GYP_GENERATORS=ninja gclient runhooks && ninja -j 10 -k1 -C ../arm64 && mkdir ../x86_64 && gn gen ../x86_64 && echo "target_cpu = \"x64\"" >> ../x86_64/args.gn && echo "angle_enable_metal=true" >> ../x86_64/args.gn && GYP_GENERATORS=ninja gclient runhooks && ninja -j 10 -k1 -C ../x86_64;
  lipo -create -output ../libEGL.dylib ../arm64/libEGL.dylib ../x86_64/libEGL.dylib && lipo -create -output ../libGLESv2.dylib ../arm64/libGLESv2.dylib ../x86_64/libGLESv2.dylib && lipo -create -output ../libc++_chrome.dylib ../arm64/libc++_chrome.dylib ../x86_64/libc++_chrome.dylib && lipo -create -output ../libchrome_zlib.dylib ../arm64/libchrome_zlib.dylib ../x86_64/libchrome_zlib.dylib && lipo -create -output ../libthird_party_abseil-cpp_absl.dylib ../arm64/libthird_party_abseil-cpp_absl.dylib ../x86_64/libthird_party_abseil-cpp_absl.dylib && lipo -create -output ../libdawn_proc.dylib ../arm64/libdawn_proc.dylib ../x86_64/libdawn_proc.dylib && lipo -create -output ../libdawn_native.dylib ../arm64/libdawn_native.dylib ../x86_64/libdawn_native.dylib && lipo -create -output ../libdawn_platform.dylib ../arm64/libdawn_platform.dylib ../x86_64/libdawn_platform.dylib;
  cd ..; clang++ /opt/local/lib/libSDL2.a main.cpp system.cpp -o sysinfo -Iangle/include -L. -I/opt/local/include -I/opt/local/include/SDL2 -std=c++17 -DCREATE_CONTEXT -lobjc -liconv -lEGL -lGLESv2 -Wl,-framework,CoreAudio -Wl,-framework,AudioToolbox -Wl,-weak_framework,CoreHaptics -Wl,-weak_framework,GameController -Wl,-framework,ForceFeedback -lobjc -Wl,-framework,CoreVideo -Wl,-framework,Cocoa -Wl,-framework,Carbon -Wl,-framework,IOKit -Wl,-weak_framework,QuartzCore -Wl,-weak_framework,Metal -fPIC -arch x86_64 -arch arm64; install_name_tool -change @rpath/libEGL.dylib @loader_path/libEGL.dylib ./sysinfo; install_name_tool -change @rpath/libGLESv2.dylib @loader_path/libGLESv2.dylib ./sysinfo; ./sysinfo;
elif [ $(uname) = "Linux" ]; then
  g++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT -static-libgcc -static-libstdc++ `pkg-config --cflags --libs sdl2` -lGL; ./sysinfo;
elif [ $(uname) = "FreeBSD" ]; then
  clang++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT `pkg-config --cflags --libs sdl2` -lGL -lkvm; ./sysinfo;
elif [ $(uname) = "DragonFly" ]; then
  g++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT -static-libgcc -static-libstdc++ `pkg-config --cflags --libs sdl2` -lGL -lkvm -lpthread; ./sysinfo;
elif [ $(uname) = "NetBSD" ]; then
  g++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT -static-libgcc -static-libstdc++ `pkg-config --cflags --libs sdl2` -I/usr/X11R7/include -Wl,-rpath,/usr/X11R7/lib -L/usr/X11R7/lib -lGL; ./sysinfo;
elif [ $(uname) = "OpenBSD" ]; then
  clang++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT `pkg-config --cflags --libs sdl2` -lGL; ./sysinfo;
elif [ $(uname) = "SunOS" ]; then
  export PKG_CONFIG_PATH=/usr/lib/64/pkgconfig && g++ main.cpp system.cpp -o sysinfo -std=c++17 -DCREATE_CONTEXT -static-libgcc `pkg-config --cflags --libs sdl2` -lGL; ./sysinfo;
else
  g++ main.cpp system.cpp -o sysinfo.exe -std=c++17 -DCREATE_CONTEXT -static-libgcc -static-libstdc++ -static `pkg-config --cflags --libs --static sdl2` -lws2_32 -ldxgi -lopengl32 -static; ./sysinfo.exe;
fi

The above script dynamically links ANGLE. I sadly deleted my script which demonstrates the actual issue when static linking it. I will spend the next hour or so getting that written and published here once I've tested it all. Is this already technically supported, but I need the code written differently? Or should this be a feature request? Thank you as always for the life saver of a library. It has saved me so much time and effort with C++ development. :D I was originally using GLFW and not SDL2 to create the required graphics context first but sadly I target SunOS in which case OpenIndiana (and perhaps other SunOS variants) do(es) not seem to have GLFW as a known package. I don't know what to do; all platforms need the same deps.

Also, could this, by any chance, be supported by both SDL2 and SDL3? I haven't migrated to SDL3 yet ...

Edit:

I found a way to get the vendor and renderer strings without the need for SDL, GLFW, or OpenGL to begin with.

I can be done running the system_profiler CLI through a pipe on macOS, like so ...

Vendor: https://github.com/time-killer-games/libsysinfo/blob/f67a59edea1beb798430322cf05485ef2d85e879/system.cpp#L899-L912

Renderer: https://github.com/time-killer-games/libsysinfo/blob/f67a59edea1beb798430322cf05485ef2d85e879/system.cpp#L952C1-L959C4

However, I am using ANGLE for another project on macOS that isn't this one, so not being able to get the GPU Vendor and Renderer wasn't the actual problem here, but rather, using OpenGL via ANGLE statically linked with SDL as a whole. I am writing a game engine which I intend to use ANGLE so the code doesn't go stale on macOS.

time-killer-games avatar Jul 19 '23 17:07 time-killer-games

We are scoping work for the SDL 3.2.0 release, so please let us know if this is a showstopper for you.

slouken avatar Oct 06 '24 17:10 slouken

What do you mean is it a showstopper? I was planning to upgrade to SDL3 eventually anyway so if this will only be supported in 3.x that won't be a huge deal to me. Thanks for the concern, though! Whether it will be a part of 3.2.x or some later version also doesn't make a big difference to me.

Also this issue really needs updating, (on my part), the repository linked in the original message is no longer relevant. But ignoring that, I guess things are explained fine how they are.

ghost avatar Oct 07 '24 12:10 ghost