|
|
|
|
|
|
|
|
|
|
|
|
|
import ctypes |
|
import os |
|
import sys |
|
import threading |
|
import unittest |
|
|
|
import torch |
|
|
|
os.environ["PYOPENGL_PLATFORM"] = "egl" |
|
import pycuda._driver |
|
from OpenGL import GL as gl |
|
from OpenGL.raw.EGL._errors import EGLError |
|
from pytorch3d.renderer.opengl import _can_import_egl_and_pycuda |
|
from pytorch3d.renderer.opengl.opengl_utils import ( |
|
_define_egl_extension, |
|
_egl_convert_to_int_array, |
|
_get_cuda_device, |
|
egl, |
|
EGLContext, |
|
global_device_context_store, |
|
) |
|
|
|
from .common_testing import TestCaseMixin |
|
|
|
MAX_EGL_HEIGHT = global_device_context_store.max_egl_height |
|
MAX_EGL_WIDTH = global_device_context_store.max_egl_width |
|
|
|
|
|
def _draw_square(r=1.0, g=0.0, b=1.0, **kwargs) -> torch.Tensor: |
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT) |
|
gl.glColor3f(r, g, b) |
|
x1, x2 = -0.5, 0.5 |
|
y1, y2 = -0.5, 0.5 |
|
gl.glRectf(x1, y1, x2, y2) |
|
out_buffer = gl.glReadPixels( |
|
0, 0, MAX_EGL_WIDTH, MAX_EGL_HEIGHT, gl.GL_RGB, gl.GL_UNSIGNED_BYTE |
|
) |
|
image = torch.frombuffer(out_buffer, dtype=torch.uint8).reshape( |
|
MAX_EGL_HEIGHT, MAX_EGL_WIDTH, 3 |
|
) |
|
return image |
|
|
|
|
|
def _draw_squares_with_context( |
|
cuda_device_id=0, result=None, thread_id=None, **kwargs |
|
) -> None: |
|
context = EGLContext(MAX_EGL_WIDTH, MAX_EGL_HEIGHT, cuda_device_id) |
|
with context.active_and_locked(): |
|
images = [] |
|
for _ in range(3): |
|
images.append(_draw_square(**kwargs).float()) |
|
if result is not None and thread_id is not None: |
|
egl_info = context.get_context_info() |
|
data = {"egl": egl_info, "images": images} |
|
result[thread_id] = data |
|
|
|
|
|
def _draw_squares_with_context_store( |
|
cuda_device_id=0, |
|
result=None, |
|
thread_id=None, |
|
verbose=False, |
|
**kwargs, |
|
) -> None: |
|
device = torch.device(f"cuda:{cuda_device_id}") |
|
context = global_device_context_store.get_egl_context(device) |
|
if verbose: |
|
print(f"In thread {thread_id}, device {cuda_device_id}.") |
|
with context.active_and_locked(): |
|
images = [] |
|
for _ in range(3): |
|
images.append(_draw_square(**kwargs).float()) |
|
if result is not None and thread_id is not None: |
|
egl_info = context.get_context_info() |
|
data = {"egl": egl_info, "images": images} |
|
result[thread_id] = data |
|
|
|
|
|
class TestDeviceContextStore(TestCaseMixin, unittest.TestCase): |
|
def test_cuda_context(self): |
|
cuda_context_1 = global_device_context_store.get_cuda_context( |
|
device=torch.device("cuda:0") |
|
) |
|
cuda_context_2 = global_device_context_store.get_cuda_context( |
|
device=torch.device("cuda:0") |
|
) |
|
cuda_context_3 = global_device_context_store.get_cuda_context( |
|
device=torch.device("cuda:1") |
|
) |
|
cuda_context_4 = global_device_context_store.get_cuda_context( |
|
device=torch.device("cuda:1") |
|
) |
|
self.assertIs(cuda_context_1, cuda_context_2) |
|
self.assertIs(cuda_context_3, cuda_context_4) |
|
self.assertIsNot(cuda_context_1, cuda_context_3) |
|
|
|
def test_egl_context(self): |
|
egl_context_1 = global_device_context_store.get_egl_context( |
|
torch.device("cuda:0") |
|
) |
|
egl_context_2 = global_device_context_store.get_egl_context( |
|
torch.device("cuda:0") |
|
) |
|
egl_context_3 = global_device_context_store.get_egl_context( |
|
torch.device("cuda:1") |
|
) |
|
egl_context_4 = global_device_context_store.get_egl_context( |
|
torch.device("cuda:1") |
|
) |
|
self.assertIs(egl_context_1, egl_context_2) |
|
self.assertIs(egl_context_3, egl_context_4) |
|
self.assertIsNot(egl_context_1, egl_context_3) |
|
|
|
|
|
class TestUtils(TestCaseMixin, unittest.TestCase): |
|
def test_load_extensions(self): |
|
|
|
_define_egl_extension("eglGetPlatformDisplayEXT", egl.EGLDisplay) |
|
|
|
|
|
with self.assertRaisesRegex(RuntimeError, "Cannot find EGL extension"): |
|
_define_egl_extension("eglFakeExtensionEXT", egl.EGLBoolean) |
|
|
|
def test_get_cuda_device(self): |
|
|
|
device = _get_cuda_device(0) |
|
self.assertIsNotNone(device) |
|
|
|
with self.assertRaisesRegex(ValueError, "Device 10000 not available"): |
|
_get_cuda_device(10000) |
|
|
|
def test_egl_convert_to_int_array(self): |
|
egl_attributes = {egl.EGL_RED_SIZE: 8} |
|
attribute_array = _egl_convert_to_int_array(egl_attributes) |
|
self.assertEqual(attribute_array._type_, ctypes.c_int) |
|
self.assertEqual(attribute_array._length_, 3) |
|
self.assertEqual(attribute_array[0], egl.EGL_RED_SIZE) |
|
self.assertEqual(attribute_array[1], 8) |
|
self.assertEqual(attribute_array[2], egl.EGL_NONE) |
|
|
|
|
|
class TestOpenGLSingleThreaded(TestCaseMixin, unittest.TestCase): |
|
def test_draw_square(self): |
|
context = EGLContext(width=MAX_EGL_WIDTH, height=MAX_EGL_HEIGHT) |
|
with context.active_and_locked(): |
|
rendering_result = _draw_square().float() |
|
expected_result = torch.zeros( |
|
(MAX_EGL_WIDTH, MAX_EGL_HEIGHT, 3), dtype=torch.float |
|
) |
|
start_px = int(MAX_EGL_WIDTH / 4) |
|
end_px = int(MAX_EGL_WIDTH * 3 / 4) |
|
expected_result[start_px:end_px, start_px:end_px, 0] = 255.0 |
|
expected_result[start_px:end_px, start_px:end_px, 2] = 255.0 |
|
|
|
self.assertTrue(torch.all(expected_result == rendering_result)) |
|
|
|
def test_render_two_squares(self): |
|
|
|
context = EGLContext(width=MAX_EGL_WIDTH, height=MAX_EGL_HEIGHT) |
|
with context.active_and_locked(): |
|
red_square = _draw_square(r=1.0, g=0.0, b=0.0) |
|
blue_square = _draw_square(r=0.0, g=0.0, b=1.0) |
|
|
|
start_px = int(MAX_EGL_WIDTH / 4) |
|
end_px = int(MAX_EGL_WIDTH * 3 / 4) |
|
|
|
self.assertTrue( |
|
torch.all( |
|
red_square[start_px:end_px, start_px:end_px] |
|
== torch.tensor([255, 0, 0]) |
|
) |
|
) |
|
self.assertTrue( |
|
torch.all( |
|
blue_square[start_px:end_px, start_px:end_px] |
|
== torch.tensor([0, 0, 255]) |
|
) |
|
) |
|
|
|
|
|
class TestOpenGLMultiThreaded(TestCaseMixin, unittest.TestCase): |
|
def test_multiple_renders_single_gpu_single_context(self): |
|
_draw_squares_with_context() |
|
|
|
def test_multiple_renders_single_gpu_context_store(self): |
|
_draw_squares_with_context_store() |
|
|
|
def test_render_two_threads_single_gpu(self): |
|
self._render_two_threads_single_gpu(_draw_squares_with_context) |
|
|
|
def test_render_two_threads_single_gpu_context_store(self): |
|
self._render_two_threads_single_gpu(_draw_squares_with_context_store) |
|
|
|
def test_render_two_threads_two_gpus(self): |
|
self._render_two_threads_two_gpus(_draw_squares_with_context) |
|
|
|
def test_render_two_threads_two_gpus_context_store(self): |
|
self._render_two_threads_two_gpus(_draw_squares_with_context_store) |
|
|
|
def _render_two_threads_single_gpu(self, draw_fn): |
|
result = [None] * 2 |
|
thread1 = threading.Thread( |
|
target=draw_fn, |
|
kwargs={ |
|
"cuda_device_id": 0, |
|
"result": result, |
|
"thread_id": 0, |
|
"r": 1.0, |
|
"g": 0.0, |
|
"b": 0.0, |
|
}, |
|
) |
|
thread2 = threading.Thread( |
|
target=draw_fn, |
|
kwargs={ |
|
"cuda_device_id": 0, |
|
"result": result, |
|
"thread_id": 1, |
|
"r": 0.0, |
|
"g": 1.0, |
|
"b": 0.0, |
|
}, |
|
) |
|
|
|
thread1.start() |
|
thread2.start() |
|
thread1.join() |
|
thread2.join() |
|
|
|
start_px = int(MAX_EGL_WIDTH / 4) |
|
end_px = int(MAX_EGL_WIDTH * 3 / 4) |
|
red_squares = torch.stack(result[0]["images"], dim=0)[ |
|
:, start_px:end_px, start_px:end_px |
|
] |
|
green_squares = torch.stack(result[1]["images"], dim=0)[ |
|
:, start_px:end_px, start_px:end_px |
|
] |
|
self.assertTrue(torch.all(red_squares == torch.tensor([255.0, 0.0, 0.0]))) |
|
self.assertTrue(torch.all(green_squares == torch.tensor([0.0, 255.0, 0.0]))) |
|
|
|
def _render_two_threads_two_gpus(self, draw_fn): |
|
|
|
|
|
|
|
result = [None] * 2 |
|
thread1 = threading.Thread( |
|
target=draw_fn, |
|
kwargs={ |
|
"cuda_device_id": 0, |
|
"result": result, |
|
"thread_id": 0, |
|
"r": 1.0, |
|
"g": 0.0, |
|
"b": 0.0, |
|
}, |
|
) |
|
thread2 = threading.Thread( |
|
target=draw_fn, |
|
kwargs={ |
|
"cuda_device_id": 1, |
|
"result": result, |
|
"thread_id": 1, |
|
"r": 0.0, |
|
"g": 1.0, |
|
"b": 0.0, |
|
}, |
|
) |
|
thread1.start() |
|
thread2.start() |
|
thread1.join() |
|
thread2.join() |
|
self.assertNotEqual( |
|
result[0]["egl"]["context"].address, result[1]["egl"]["context"].address |
|
) |
|
|
|
start_px = int(MAX_EGL_WIDTH / 4) |
|
end_px = int(MAX_EGL_WIDTH * 3 / 4) |
|
red_squares = torch.stack(result[0]["images"], dim=0)[ |
|
:, start_px:end_px, start_px:end_px |
|
] |
|
green_squares = torch.stack(result[1]["images"], dim=0)[ |
|
:, start_px:end_px, start_px:end_px |
|
] |
|
self.assertTrue(torch.all(red_squares == torch.tensor([255.0, 0.0, 0.0]))) |
|
self.assertTrue(torch.all(green_squares == torch.tensor([0.0, 255.0, 0.0]))) |
|
|
|
def test_render_multi_thread_multi_gpu(self): |
|
|
|
|
|
|
|
|
|
n_gpus = torch.cuda.device_count() |
|
n_threads = 10 |
|
kwargs = { |
|
"r": 1.0, |
|
"g": 0.0, |
|
"b": 0.0, |
|
"verbose": True, |
|
} |
|
|
|
threads = [] |
|
for thread_id in range(n_threads): |
|
kwargs.update( |
|
{"cuda_device_id": thread_id % n_gpus, "thread_id": thread_id} |
|
) |
|
threads.append( |
|
threading.Thread( |
|
target=_draw_squares_with_context_store, kwargs=dict(kwargs) |
|
) |
|
) |
|
|
|
for thread in threads: |
|
thread.start() |
|
for thread in threads: |
|
thread.join() |
|
|
|
|
|
class TestOpenGLUtils(TestCaseMixin, unittest.TestCase): |
|
@classmethod |
|
def tearDownClass(cls): |
|
global_device_context_store.set_context_data(torch.device("cuda:0"), None) |
|
|
|
def test_device_context_store(self): |
|
|
|
device = torch.device("cuda:0") |
|
global_device_context_store.set_context_data(device, 123) |
|
|
|
self.assertEqual(global_device_context_store.get_context_data(device), 123) |
|
|
|
self.assertEqual( |
|
global_device_context_store.get_context_data(torch.device("cuda:1")), None |
|
) |
|
|
|
|
|
|
|
egl_ctx = global_device_context_store.get_egl_context(device) |
|
cuda_ctx = global_device_context_store.get_cuda_context(device) |
|
egl_ctx.release() |
|
cuda_ctx.detach() |
|
|
|
|
|
|
|
|
|
global_device_context_store._cuda_contexts = {} |
|
global_device_context_store._egl_contexts = {} |
|
|
|
egl_ctx = global_device_context_store.get_egl_context(device) |
|
cuda_ctx = global_device_context_store.get_cuda_context(device) |
|
global_device_context_store.release() |
|
with self.assertRaisesRegex(EGLError, "EGL_NOT_INITIALIZED"): |
|
egl_ctx.release() |
|
with self.assertRaisesRegex(pycuda._driver.LogicError, "cannot detach"): |
|
cuda_ctx.detach() |
|
|
|
def test_no_egl_error(self): |
|
|
|
|
|
del os.environ["PYOPENGL_PLATFORM"] |
|
modules = list(sys.modules) |
|
for m in modules: |
|
if "OpenGL" in m: |
|
del sys.modules[m] |
|
import OpenGL.GL |
|
|
|
self.assertFalse(_can_import_egl_and_pycuda()) |
|
|
|
|
|
modules = list(sys.modules) |
|
for m in modules: |
|
if "OpenGL" in m: |
|
del sys.modules[m] |
|
|
|
os.environ["PYOPENGL_PLATFORM"] = "egl" |
|
self.assertTrue(_can_import_egl_and_pycuda()) |
|
|
|
def test_egl_release_error(self): |
|
|
|
|
|
|
|
ctx1 = EGLContext(width=100, height=100) |
|
ctx2 = EGLContext(width=100, height=100) |
|
|
|
ctx1.release() |
|
with self.assertRaisesRegex(EGLError, "EGL_NOT_INITIALIZED"): |
|
ctx2.release() |
|
|