Skip to content

Commit

Permalink
Add opengl context and X11 dependency for use in render engine gl (Ro…
Browse files Browse the repository at this point in the history
  • Loading branch information
tehbelinda authored Mar 27, 2020
1 parent 139f56c commit 8b1a90e
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 14 deletions.
15 changes: 14 additions & 1 deletion geometry/render/gl_renderer/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ load(
load("//tools/lint:lint.bzl", "add_lint_tests")
load(
"defs.bzl",
"drake_cc_googletest_gl_ubuntu_only",
"drake_cc_library_gl_ubuntu_only",
"drake_cc_package_library_gl_per_os",
)
Expand All @@ -34,7 +35,12 @@ drake_cc_library_gl_ubuntu_only(
"opengl_includes.h",
],
visibility = ["//visibility:private"],
deps = ["@opengl"],
deps = [
"//common:essential",
"//common:scope_exit",
"@opengl",
"@x11",
],
)

# The pure OpenGL-based render engine implementation.
Expand Down Expand Up @@ -63,6 +69,13 @@ drake_cc_library(
}),
)

drake_cc_googletest_gl_ubuntu_only(
name = "opengl_context_test",
deps = [
":opengl_context",
],
)

drake_cc_googletest(
name = "render_engine_gl_test",
args = select({
Expand Down
5 changes: 5 additions & 0 deletions geometry/render/gl_renderer/defs.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
load(
"@drake//tools/skylark:drake_cc.bzl",
"drake_cc_googletest",
"drake_cc_library",
"drake_cc_package_library",
)
Expand All @@ -8,6 +9,10 @@ load(
"DISTRIBUTION",
)

def drake_cc_googletest_gl_ubuntu_only(**kwargs):
if DISTRIBUTION == "ubuntu":
drake_cc_googletest(**kwargs)

def drake_cc_library_gl_ubuntu_only(**kwargs):
if DISTRIBUTION == "ubuntu":
drake_cc_library(**kwargs)
Expand Down
163 changes: 160 additions & 3 deletions geometry/render/gl_renderer/opengl_context.cc
Original file line number Diff line number Diff line change
@@ -1,16 +1,173 @@
#include "drake/geometry/render/gl_renderer/opengl_context.h"

// TODO(tehbelinda): Move this to opengl_includes.h in follow-up PR.
#include <algorithm>
#include <cstring>
#include <stdexcept>
#include <string>
#include <utility>

// Note: This is intentionally included here since it's only needed at the
// implementation level, and not in a grouping of more generic headers like
// opengl_includes.h. See opengl_context.h for where pimpl is applied.
#include <GL/glx.h>

#include "drake/common/drake_assert.h"
#include "drake/common/drake_throw.h"
#include "drake/common/scope_exit.h"
#include "drake/common/text_logging.h"

namespace drake {
namespace geometry {
namespace render {
namespace gl {
namespace internal {

namespace {

// Helper function for loading OpenGL extension functions. For more info, see:
// https://www.opengl.org/archives/resources/features/OGLextensions.
template <class F>
F* GetGlXFunctionArb(const char* func_name) {
// We must copy the string to a GLubyte buffer to avoid strict aliasing rules.
// https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8
constexpr int kBufferSize = 128;
DRAKE_ASSERT(strlen(func_name) < kBufferSize);
GLubyte gl_func_name[kBufferSize] = {};
std::memcpy(gl_func_name, func_name, strlen(func_name) + 1);
return reinterpret_cast<F*>(glXGetProcAddressARB(gl_func_name));
}

void GlDebugCallback(GLenum, GLenum type, GLuint, GLenum severity, GLsizei,
const GLchar* message, const void*) {
const char* output =
"GL CALLBACK: {:s} type = 0x{:x}, severity = 0x{:x}, message = {:s}";
if (type == GL_DEBUG_TYPE_ERROR) {
drake::log()->error(output, "** GL_ERROR **", type, severity, message);
} else {
drake::log()->info(output, "", type, severity, message);
}
}

} // namespace

class OpenGlContext::Impl {
public:
// Open an X display and initialize an OpenGL context. The display will be
// open and ready for offscreen rendering, but no window is visible.
explicit Impl(bool debug) {
// See Offscreen Rendering section here:
// https://sidvind.com/index.php?title=Opengl/windowless

// Get framebuffer configs.
const int kVisualAttribs[] = {None};
int fb_count = 0;
GLXFBConfig* fb_configs = glXChooseFBConfig(
display(), DefaultScreen(display()), kVisualAttribs, &fb_count);
ScopeExit guard([fb_configs]() { XFree(fb_configs); });
if (fb_configs == nullptr) {
throw std::runtime_error(
"Error initializing OpenGL Context for RenderEngineGL; no suitable "
"frame buffer configuration found.");
}

// Create an OpenGL context.
const int kContextAttribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 3, None};
auto glXCreateContextAttribsARB =
GetGlXFunctionArb<GLXContext(Display*, GLXFBConfig, GLXContext, Bool,
const int*)>("glXCreateContextAttribsARB");

// Since we have provided attributes in the call to glXChooseFBConfig, we
// are guaranteed to have a valid result in fb_configs as we have already
// checked for null.
DRAKE_DEMAND(fb_count > 0);
// NOTE: The consts True and False come from gl/glx.h (indirectly), but
// ultimately from X11/Xlib.h.
context_ = glXCreateContextAttribsARB(display(), fb_configs[0], 0, True,
kContextAttribs);
if (context_ == nullptr) {
throw std::runtime_error(
"Error initializing OpenGL Context for RenderEngineGL; failed to "
"create context via glXCreateContextAttribsARB.");
}

XSync(display(), False);

// Make it the current context.
MakeCurrent();

// Enable debug.
if (debug) {
drake::log()->info("Vendor: {}", glGetString(GL_VENDOR));
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(GlDebugCallback, 0);
}
}

~Impl() {
glXDestroyContext(display(), context_);
}

void MakeCurrent() {
if (glXGetCurrentContext() != context_ &&
!glXMakeCurrent(display(), None, context_)) {
throw std::runtime_error("Error making an OpenGL context current");
}
}

static GLint max_texture_size() {
GLint res{-1};
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &res);
return res;
}

static GLint max_renderbuffer_size() {
GLint res{-1};
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &res);
return res;
}

static GLint max_allowable_texture_size() {
// TODO(duy): Take into account CUDA limits.
return std::min(max_texture_size(), max_renderbuffer_size());
}

private:
static Display* display() {
// Turn Display into a singleton to make CI happy, since when we close and
// reopen the display on CI, we can't request a new OpenGL context.
// This pattern won't call the corresponding `XCloseDisplay()` when the
// program exits, but it seems not so evil to skip that.
// (https://linux.die.net/man/3/xclosedisplay)
// TODO(duy): If problems crop up in the future, this can/should be
// investigated.
static Display* g_display = XOpenDisplay(0);
DRAKE_THROW_UNLESS(g_display != nullptr);
return g_display;
}

GLXContext context_{nullptr};
};

OpenGlContext::OpenGlContext(bool debug)
: impl_(new OpenGlContext::Impl(debug)) {}

OpenGlContext::~OpenGlContext() = default;

void OpenGlContext::MakeCurrent() { impl_->MakeCurrent(); }

GLint OpenGlContext::max_texture_size() {
return OpenGlContext::Impl::max_texture_size();
}

GLint OpenGlContext::max_renderbuffer_size() {
return OpenGlContext::Impl::max_renderbuffer_size();
}
} // namespace gl

GLint OpenGlContext::max_allowable_texture_size() {
return OpenGlContext::Impl::max_allowable_texture_size();
}

} // namespace internal
} // namespace render
} // namespace geometry
} // namespace drake
38 changes: 34 additions & 4 deletions geometry/render/gl_renderer/opengl_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,45 @@
namespace drake {
namespace geometry {
namespace render {
namespace gl {
namespace internal {

/** Temporary dummy class for building dummy RenderEngineGl dependencies. */
/** Handle OpenGL context initialization, clean-up, and generic OpenGL queries.
This class creates and owns a new context upon construction. Rendering classes
need to keep their own OpenGlContext and ensure that they switch to it using
`MakeCurrent()` before any OpenGL calls.
*/
class OpenGlContext {
public:
static void Dummy() {}
/** Constructor. Initializes an OpenGL context.
@param debug If debug is true, the OpenGl context will be a "debug" context,
in that the OpenGl implementation's errors will be written to the Drake log.
See https://www.khronos.org/opengl/wiki/Debug_Output for more information.
*/
explicit OpenGlContext(bool debug = false);

~OpenGlContext();

/** Makes this context current.
@throw std::runtime_error if not successful. */
void MakeCurrent();

/** Returns the specified values for the current OpenGL context.
@note Even if invoked via an instance, they may not reflect that instance's
configuration if the instance differs from whichever context is current. */
static GLint max_texture_size();
static GLint max_renderbuffer_size();
static GLint max_allowable_texture_size();

private:
// Note: we are dependent on `GL/glx.h` but don't want to let that bleed into
// other code. So, we pimpl this up so that glx.h lives only in the
// implementation.
class Impl;

std::unique_ptr<Impl> impl_;
};

} // namespace gl
} // namespace internal
} // namespace render
} // namespace geometry
} // namespace drake
11 changes: 6 additions & 5 deletions geometry/render/gl_renderer/opengl_includes.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/// @file
///
/// Provides a one-stop shop for pulling in the OpenGl symbols in with the
/// required extensions. Handles the ordering and the required macros in one
/// place so dependent headers don't have to worry about it.
/** @file
Provides a one-stop shop for pulling in the OpenGl symbols with the required
extensions. By design, this includes only the baseline OpenGl headers and
handles their ordering. If code requires more elaborate libraries, it is
responsible for pulling them in directly.
*/

#pragma once

Expand Down
1 change: 0 additions & 1 deletion geometry/render/gl_renderer/render_engine_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ using systems::sensors::ImageLabel16I;
using systems::sensors::ImageRgba8U;

RenderEngineGl::RenderEngineGl() {
gl::OpenGlContext::Dummy();
}

void RenderEngineGl::UpdateViewpoint(const RigidTransformd&) {}
Expand Down
34 changes: 34 additions & 0 deletions geometry/render/gl_renderer/test/opengl_context_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "drake/geometry/render/gl_renderer/opengl_context.h"

#include <gtest/gtest.h>

namespace drake {
namespace geometry {
namespace render {
namespace internal {
namespace {

// Tests that an OpenGL context can be obtained.
GTEST_TEST(OpenGlContext, GetContext) {
OpenGlContext opengl_context{};
EXPECT_FALSE(glIsEnabled(GL_DEBUG_OUTPUT));
opengl_context.MakeCurrent();
// Test switching contexts and enabling debug mode.
OpenGlContext debug_context{true};
EXPECT_TRUE(glIsEnabled(GL_DEBUG_OUTPUT));
debug_context.MakeCurrent();
// Enable something on the current context.
EXPECT_FALSE(glIsEnabled(GL_BLEND));
glEnable(GL_BLEND);
EXPECT_TRUE(glIsEnabled(GL_BLEND));
// Back to original context.
opengl_context.MakeCurrent();
// The previous enable should have no effect on this context.
EXPECT_FALSE(glIsEnabled(GL_BLEND));
}

} // namespace
} // namespace internal
} // namespace render
} // namespace geometry
} // namespace drake
1 change: 1 addition & 0 deletions setup/ubuntu/binary_distribution/packages-bionic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ libtheora0
libtiff5
libtinyxml2-dev
libtinyxml2.6.2v5
libx11-6
libxml2
libxt6
libyaml-cpp0.5v5
Expand Down
1 change: 1 addition & 0 deletions setup/ubuntu/source_distribution/packages-bionic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ libtheora-dev
libtiff5-dev
libtinyxml-dev
libtool
libx11-dev
libxml2-dev
libxt-dev
libyaml-cpp-dev
Expand Down
12 changes: 12 additions & 0 deletions tools/install/libdrake/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ cc_library(
],
)

# Depend on X11 iff on Ubuntu and not MacOS.
cc_library(
name = "x11_deps",
deps = select({
"//conditions:default": [
"@x11",
],
"//tools/cc_toolchain:apple": [],
}),
)

# Provide a cc_library target that provides libdrake.so, its headers, its
# header-only dependencies, and its required *.so's. This is aliased by
# `//:drake_shared_library`, which is what downstream users will consume if
Expand Down Expand Up @@ -246,6 +257,7 @@ cc_library(
":mosek_deps",
":nlopt_deps",
":vtk_deps",
":x11_deps",
"//common:drake_marker_shared_library",
"@ignition_math",
"@lcm",
Expand Down
3 changes: 3 additions & 0 deletions tools/workspace/default.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ load("@drake//tools/workspace/tinyxml2:repository.bzl", "tinyxml2_repository")
load("@drake//tools/workspace/tinyxml:repository.bzl", "tinyxml_repository")
load("@drake//tools/workspace/uritemplate_py:repository.bzl", "uritemplate_py_repository") # noqa
load("@drake//tools/workspace/vtk:repository.bzl", "vtk_repository")
load("@drake//tools/workspace/x11:repository.bzl", "x11_repository")
load("@drake//tools/workspace/yaml_cpp:repository.bzl", "yaml_cpp_repository")
load("@drake//tools/workspace/zlib:repository.bzl", "zlib_repository")

Expand Down Expand Up @@ -257,6 +258,8 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS):
uritemplate_py_repository(name = "uritemplate_py", mirrors = mirrors)
if "vtk" not in excludes:
vtk_repository(name = "vtk", mirrors = mirrors)
if "x11" not in excludes:
x11_repository(name = "x11")
if "yaml_cpp" not in excludes:
yaml_cpp_repository(name = "yaml_cpp")
if "zlib" not in excludes:
Expand Down
Loading

0 comments on commit 8b1a90e

Please sign in to comment.