From cb5ac4372363b49af5bf72dc9ea327c54a594623 Mon Sep 17 00:00:00 2001 From: julienvalentin Date: Sat, 21 Sep 2019 05:43:39 -0700 Subject: [PATCH] EGL utils PiperOrigin-RevId: 270440741 --- tensorflow_graphics/rendering/opengl/BUILD | 23 +- .../rendering/opengl/egl_offscreen_context.cc | 2 +- .../rendering/opengl/egl_util.cc | 161 ++++++++++ .../rendering/opengl/egl_util.h | 54 ++++ .../rendering/opengl/egl_util_test.cc | 299 ++++++++++++++++++ 5 files changed, 536 insertions(+), 3 deletions(-) create mode 100644 tensorflow_graphics/rendering/opengl/egl_util.cc create mode 100644 tensorflow_graphics/rendering/opengl/egl_util.h create mode 100644 tensorflow_graphics/rendering/opengl/egl_util_test.cc diff --git a/tensorflow_graphics/rendering/opengl/BUILD b/tensorflow_graphics/rendering/opengl/BUILD index 9d06e00d6..be94f75e4 100644 --- a/tensorflow_graphics/rendering/opengl/BUILD +++ b/tensorflow_graphics/rendering/opengl/BUILD @@ -55,8 +55,8 @@ cc_library( hdrs = ["egl_offscreen_context.h"], deps = [ ":EGL_headers", + ":egl_util", ":gl_macros", - "//third_party/GL/util:egl_util", "//third_party/tensorflow/core:lib", ], ) @@ -76,6 +76,26 @@ cc_test( ], ) +cc_library( + name = "egl_util", + srcs = ["egl_util.cc"], + hdrs = ["egl_util.h"], + visibility = ["//visibility:public"], + deps = [ + ":EGL_headers", + ], +) + +cc_test( + name = "egl_util_test", + srcs = ["egl_util_test.cc"], + deps = [ + ":EGL_headers", + ":egl_util", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "gl_macros", hdrs = ["gl_macros.h"], @@ -91,7 +111,6 @@ cc_library( deps = [ "//third_party/GL/native:EGL", # buildcleaner: keep "//third_party/GL/native:GLESv3", # buildcleaner: keep - "//third_party/GL/native:nvidia_egl_device_isolation_mitigation", # buildcleaner: keep ], alwayslink = 1, ) diff --git a/tensorflow_graphics/rendering/opengl/egl_offscreen_context.cc b/tensorflow_graphics/rendering/opengl/egl_offscreen_context.cc index fba86cbc1..4708efc83 100644 --- a/tensorflow_graphics/rendering/opengl/egl_offscreen_context.cc +++ b/tensorflow_graphics/rendering/opengl/egl_offscreen_context.cc @@ -16,7 +16,7 @@ limitations under the License. #include -#include "GL/util/egl_util.h" +#include "tensorflow_graphics/rendering/opengl/egl_util.h" #include "tensorflow_graphics/rendering/opengl/gl_macros.h" #include "tensorflow/core/lib/gtl/cleanup.h" diff --git a/tensorflow_graphics/rendering/opengl/egl_util.cc b/tensorflow_graphics/rendering/opengl/egl_util.cc new file mode 100644 index 000000000..ee88bac92 --- /dev/null +++ b/tensorflow_graphics/rendering/opengl/egl_util.cc @@ -0,0 +1,161 @@ +/* Copyright 2019 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow_graphics/rendering/opengl/egl_util.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +// Maximum number of EGL devices to query. Currently the maximum number of +// devices a machine could have is 16, but we allow space for 32. +constexpr int kMaxDevices = 32; + +// Helper function for loading EGL extensions. +template +T LoadEGLFunction(const char* func_name) { + if (T func = reinterpret_cast(eglGetProcAddress(func_name))) { + return func; + } else { + std::cerr << "Failed to load EGL function " << func_name << "\n"; + return nullptr; + } +} + +// Mutex used to lock the display_reference_map and eglInitialize/egTerminate +// calls. +std::mutex* get_display_mutex() { + static std::mutex* display_reference_mutex = new std::mutex(); + return display_reference_mutex; +} + +std::unordered_map* get_display_reference_map() { + static std::unordered_map* display_reference_map = + new std::unordered_map(); + return display_reference_map; +} + +void IncrementDisplayRefCount(EGLDisplay display) { + auto* display_map = get_display_reference_map(); + auto iter_inserted = display_map->emplace(display, 0).first; + ++iter_inserted->second; +} + +// Helper to decrement reference count for provided EGLDisplay. Returns the +// reference count after decrementing the provided counter. If the EGLDisplay is +// not found, return -1. +int DecrementDisplayRefCount(EGLDisplay display) { + auto* display_map = get_display_reference_map(); + auto it = display_map->find(display); + if (it != display_map->end()) { + int ref_count = --it->second; + if (ref_count == 0) display_map->erase(it); + return ref_count; + } else { + return -1; + } +} + +EGLBoolean TerminateInitializedEGLDisplayNoLock(EGLDisplay display) { + if (display == EGL_NO_DISPLAY) { + return eglTerminate(display); + } + int ref_count = DecrementDisplayRefCount(display); + if (ref_count == 0) { + return eglTerminate(display); + } else if (ref_count > 0) { + return EGL_TRUE; + } else { + std::cerr << "Could not find EGLDisplay Reference count! Either we didn't " + "create EGLDisplay with CreateInitializedEGLDisplay() or we " + "have already terminated the display.\n"; + return EGL_FALSE; + } +} + +} // namespace + +extern "C" EGLDisplay CreateInitializedEGLDisplayAtIndex(int device_index) { + // Load EGL extension functions for querying EGL devices manually. This + // extension isn't officially supported in EGL 1.4, so try and manually + // load them using eglGetProcAddress. + auto eglQueryDevicesEXT = + LoadEGLFunction("eglQueryDevicesEXT"); + if (eglQueryDevicesEXT == nullptr) return EGL_NO_DISPLAY; + + auto eglGetPlatformDisplayEXT = + LoadEGLFunction( + "eglGetPlatformDisplayEXT"); + if (eglGetPlatformDisplayEXT == nullptr) return EGL_NO_DISPLAY; + + EGLDeviceEXT egl_devices[kMaxDevices]; + EGLint num_devices = 0; + auto egl_error = eglGetError(); + if (!eglQueryDevicesEXT(kMaxDevices, egl_devices, &num_devices) || + egl_error != EGL_SUCCESS) { + std::cerr << "eglQueryDevicesEXT Failed. EGL error " << std::hex + << eglGetError() << "\n"; + return EGL_NO_DISPLAY; + } + + // Go through each device and try to initialize the display. + for (EGLint i = 0; i < num_devices; ++i) { + // First try and get a valid EGL display. + auto display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, + egl_devices[i], nullptr); + if (eglGetError() == EGL_SUCCESS && display != EGL_NO_DISPLAY) { + // Aquire lock before calling eglInitialize() and incrementing ref count. + std::lock_guard display_guard(*get_display_mutex()); + + // Now try to initialize the display. This can fail when we don't have + // access to the device. + int major, minor; + EGLBoolean initialized = eglInitialize(display, &major, &minor); + if (eglGetError() == EGL_SUCCESS && initialized == EGL_TRUE) { + IncrementDisplayRefCount(display); + if (--device_index < 0) { + return display; + } else { + TerminateInitializedEGLDisplayNoLock(display); + } + } + } + } + + std::cerr << "Failed to create and initialize a valid EGL display! " + << "Devices tried: " << num_devices << "\n"; + return EGL_NO_DISPLAY; +} + +extern "C" EGLDisplay CreateInitializedEGLDisplay() { + return CreateInitializedEGLDisplayAtIndex(0); +} + +extern "C" EGLBoolean TerminateInitializedEGLDisplay(EGLDisplay display) { + // Acquire lock before terminating and decrementing display ref count. + std::lock_guard display_guard(*get_display_mutex()); + return TerminateInitializedEGLDisplayNoLock(display); +} + +extern "C" void ShutDownEGLSubsystem() { + delete get_display_reference_map(); + delete get_display_mutex(); +} diff --git a/tensorflow_graphics/rendering/opengl/egl_util.h b/tensorflow_graphics/rendering/opengl/egl_util.h new file mode 100644 index 000000000..2d5bb38cc --- /dev/null +++ b/tensorflow_graphics/rendering/opengl/egl_util.h @@ -0,0 +1,54 @@ +/* Copyright 2019 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef THIRD_PARTY_PY_TENSORFLOW_GRAPHICS_RENDERING_OPENGL_EGL_UTIL_H_ +#define THIRD_PARTY_PY_TENSORFLOW_GRAPHICS_RENDERING_OPENGL_EGL_UTIL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Creates and initializes an EGL display at the specified device_index. Unlike +// the standard eglGetDisplay(), this function takes a device_index, iterates +// through all the available devices on the machine using EGL extensions, and +// returns the Nth successfully initialized EGLDisplay. This allows us to get a +// valid EGL display on multi-GPU machines, where we limit access to a sub-set +// of the available GPU devices. Returns an initialized EGLDisplay or +// EGL_NO_DISPLAY on error. +EGLDisplay CreateInitializedEGLDisplayAtIndex(int device_index); + +// Helper function to create EGL display at device index 0. +EGLDisplay CreateInitializedEGLDisplay(void); + +// Helper function to only call eglTerminate() once all instances created from +// CreateInitializedEGLDisplay() have been terminated. This is necessary because +// calling eglTerminate will invalidate *all* contexts associated with a given +// display within the same address space. +EGLBoolean TerminateInitializedEGLDisplay(EGLDisplay display); + +// Helper function that unloads any remaining resources used for internal +// bookkeeping. Ordinary user code generally should not need to call this, +// but it is useful when, say, using this code as part of a DSO that is +// loaded and unloaded repeatedly. This function must not be called more +// than once per process (or DSO load). It should generally be called just +// before exit. +void ShutDownEGLSubsystem(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // THIRD_PARTY_PY_TENSORFLOW_GRAPHICS_RENDERING_OPENGL_EGL_UTIL_H_ diff --git a/tensorflow_graphics/rendering/opengl/egl_util_test.cc b/tensorflow_graphics/rendering/opengl/egl_util_test.cc new file mode 100644 index 000000000..95a3fffbd --- /dev/null +++ b/tensorflow_graphics/rendering/opengl/egl_util_test.cc @@ -0,0 +1,299 @@ +/* Copyright 2019 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow_graphics/rendering/opengl/egl_util.h" + +#include + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +// typedefs for other required EGL functions. +using EGLGetErrorFunc = EGLint (*)(void); +using EGLGetProcAddressFunc = + __eglMustCastToProperFunctionPointerType (*)(const char* func_name); +using EGLInitializeFunc = EGLBoolean (*)(EGLDisplay display, EGLint* major, + EGLint* minor); +using EGLTerminateFunc = EGLBoolean (*)(EGLDisplay display); + +// Global function pointers that we can override at runtime. +EGLGetErrorFunc egl_get_error_func = nullptr; +EGLGetProcAddressFunc egl_get_proc_address_func = nullptr; +EGLInitializeFunc egl_initialize_func = nullptr; +EGLTerminateFunc egl_terminate_func = nullptr; + +PFNEGLQUERYDEVICESEXTPROC egl_query_devices_ext_proc = nullptr; +PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext_proc = nullptr; + +// Implemention for EGL functions used in egl_util.cc. These forward to test +// functions defined at runtime that can be mocked in unit tests. +extern "C" { +EGLAPI EGLint EGLAPIENTRY eglGetError(void) { + if (egl_get_error_func == nullptr) std::abort(); + return egl_get_error_func(); +} + +EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress (const char *procname) { + if (egl_get_proc_address_func == nullptr) std::abort(); + return egl_get_proc_address_func(procname); +} + +EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint* major, + EGLint* minor) { + if (egl_initialize_func == nullptr) std::abort(); + return egl_initialize_func(dpy, major, minor); +} + +EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy) { + if (egl_terminate_func == nullptr) std::abort(); + return egl_terminate_func(dpy); +} +} // extern "C" + +// Testing implementation of EGLDisplay. Stores ID which can be used to identify +// a given device. +struct EGLTestDisplay { + int id; +}; + +// Testing implementation of EGLDevice. Stores ID which can be used to identify +// a given device. +struct EGLTestDevice { + int id; + EGLTestDisplay display; +}; + +// Device ID's set for each device. Used to check we initialized the correct +// device in tests. +constexpr int kTestDeviceId0 = 0; +constexpr int kTestDeviceId1 = 1; +constexpr int kTestDeviceId2 = 2; + +// Default array of EGLDevices, used in EGL function implementations. +static EGLTestDevice egl_test_devices[] = {{kTestDeviceId0, {kTestDeviceId0}}, + {kTestDeviceId1, {kTestDeviceId1}}, + {kTestDeviceId2, {kTestDeviceId2}}}; +constexpr std::size_t kNumDevices = + sizeof(egl_test_devices) / sizeof(egl_test_devices[0]); + +__eglMustCastToProperFunctionPointerType DefaultEGLGetProcAddress( + const char* func_name) { + if (std::strcmp(func_name, "eglQueryDevicesEXT") == 0) { + return reinterpret_cast<__eglMustCastToProperFunctionPointerType>( + egl_query_devices_ext_proc); + } else if (std::strcmp(func_name, "eglGetPlatformDisplayEXT") == 0) { + return reinterpret_cast<__eglMustCastToProperFunctionPointerType>( + egl_get_platform_display_ext_proc); + } else { + return nullptr; + } +} + +EGLBoolean DefaultEGLInitialize(EGLDisplay display, EGLint* major, + EGLint* minor) { + if (major) *major = 1; + if (minor) *minor = 4; + return std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [display](const EGLTestDevice& device) { + return &device.display == display; + }) != std::end(egl_test_devices); +} + +EGLBoolean DefaultEGLTerminate(EGLDisplay display) { + if (display == EGL_NO_DISPLAY) return EGL_FALSE; + return std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [display](const EGLTestDevice& device) { + return &device.display == display; + }) != std::end(egl_test_devices); +} + +EGLBoolean DefaultEGLQueryDevicesEXT(EGLint max_devices, EGLDeviceEXT* devices, + EGLint* num_devices) { + if (devices == nullptr) std::abort(); + if (num_devices == nullptr) std::abort(); + + *num_devices = std::min( + max_devices, + std::distance(std::begin(egl_test_devices), std::end(egl_test_devices))); + std::transform(egl_test_devices, egl_test_devices + *num_devices, devices, + [](EGLTestDevice& x) { return &x; }); + return EGL_TRUE; +} + +EGLDisplay DefaultEGLGetPlatformDisplayEXT(EGLenum platform, void* device_ext, + const EGLint* attrib_list) { + if (platform == EGL_PLATFORM_DEVICE_EXT) { + auto device_it = + std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [device_ext](const EGLTestDevice& device) { + return &device == device_ext; + }); + if (device_it != std::end(egl_test_devices)) { + return &device_it->display; + } + } + return EGL_NO_DISPLAY; +} + +// Fixture to reset EGL function pointers to default implementations for each +// test. +class EGLUtilTest : public ::testing::Test { + protected: + void SetUp() override { + egl_get_error_func = []() -> EGLint { return EGL_SUCCESS; }; + egl_get_proc_address_func = DefaultEGLGetProcAddress; + egl_initialize_func = DefaultEGLInitialize; + egl_terminate_func = DefaultEGLTerminate; + egl_query_devices_ext_proc = DefaultEGLQueryDevicesEXT; + egl_get_platform_display_ext_proc = DefaultEGLGetPlatformDisplayEXT; + } + + void TearDown() override { + egl_get_error_func = nullptr; + egl_get_proc_address_func = nullptr; + egl_initialize_func = nullptr; + egl_query_devices_ext_proc = nullptr; + egl_get_platform_display_ext_proc = nullptr; + } +}; + +TEST_F(EGLUtilTest, CheckCreateEGLDisplay) { + auto display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + + // Expect to get first display. + EXPECT_EQ(kTestDeviceId0, static_cast(display)->id); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(display)); +} + +TEST_F(EGLUtilTest, CheckNoDevices) { + egl_query_devices_ext_proc = [](EGLint max_devices, EGLDeviceEXT* devices, + EGLint* num_devices) -> EGLBoolean { + if (num_devices == nullptr) std::abort(); + *num_devices = 0; + return EGL_TRUE; + }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); + + egl_query_devices_ext_proc = [](EGLint, EGLDeviceEXT*, + EGLint*) -> EGLBoolean { return EGL_FALSE; }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckFailExtensions) { + egl_get_proc_address_func = + [](const char*) -> __eglMustCastToProperFunctionPointerType { + return nullptr; + }; + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); + + // Only fail for eglGetPlatformDisplayEXT + egl_get_proc_address_func = + [](const char* func_name) -> __eglMustCastToProperFunctionPointerType { + if (std::strcmp(func_name, "eglGetPlatformDisplayEXT") == 0) { + return nullptr; + } else { + return DefaultEGLGetProcAddress(func_name); + } + }; + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckFailInitialize) { + egl_initialize_func = [](EGLDisplay, EGLint*, EGLint*) -> EGLBoolean { + return EGL_FALSE; + }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckUnavailableDevices) { + // Only return EGLDisplay for device1 and fail others. + egl_get_platform_display_ext_proc = []( + EGLenum platform, void* device_ext, + const EGLint* attrib_list) -> EGLDisplay { + auto* test_device = static_cast(device_ext); + if (platform == EGL_PLATFORM_DEVICE_EXT && + test_device->id == kTestDeviceId1) { + return &test_device->display; + } + return EGL_NO_DISPLAY; + }; + auto display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + EXPECT_EQ(kTestDeviceId1, static_cast(display)->id); + + // Now test EGLInitialize only succeeds for device2. This is the usual way to + // determine if an EGLDisplay is actually usable, as eglGetPlatformDisplayEXT + // usually succeeds, even if we can't actually use the device. + egl_get_platform_display_ext_proc = DefaultEGLGetPlatformDisplayEXT; + egl_initialize_func = [](EGLDisplay display, EGLint* major, + EGLint* minor) -> EGLBoolean { + auto* test_display = static_cast(display); + if (test_display && test_display->id == kTestDeviceId2) { + return DefaultEGLInitialize(display, major, minor); + } + return EGL_FALSE; + }; + display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + EXPECT_EQ(kTestDeviceId2, static_cast(display)->id); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(display)); +} + +TEST_F(EGLUtilTest, CheckTerminateRefCounting) { + static int call_count = 0; + egl_terminate_func = [](EGLDisplay display) -> EGLBoolean { + if (display == EGL_NO_DISPLAY) return EGL_FALSE; + call_count++; + return EGL_TRUE; + }; + auto d1 = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, d1); + + auto d2 = CreateInitializedEGLDisplay(); + EXPECT_EQ(d1, d2); + + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d1)); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d2)); + EXPECT_EQ(1, call_count); + + // Try and terminate again. Should fail. + EXPECT_EQ(EGL_FALSE, TerminateInitializedEGLDisplay(d1)); +} + +TEST_F(EGLUtilTest, CheckInitializeDisplayAtIndex) { + auto d0 = CreateInitializedEGLDisplayAtIndex(0); + ASSERT_NE(EGL_NO_DISPLAY, d0); + + auto d1 = CreateInitializedEGLDisplayAtIndex(1); + EXPECT_NE(d0, d1); + + auto invalid_device = CreateInitializedEGLDisplayAtIndex(kNumDevices+1); + EXPECT_EQ(EGL_NO_DISPLAY, invalid_device); + + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d0)); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d1)); +} + +} // namespace