source/gpu/GlUtil.h (430 lines of code) (raw):
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifdef WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
// includes below need to be in the following order:
#ifndef GLOG_NO_ABBREVIATED_SEVERITIES
#define GLOG_NO_ABBREVIATED_SEVERITIES
#endif
// windows.h
#include <windows.h>
#ifdef FB360_DEP_USE_OVR_CAPI_GLE
#include <GL/CAPI_GLE.h>
#else
#include <GL/glew.h>
#endif
#define GL_HALF_FLOAT 0x140B
#define GL_RG 0x8227
#include <GL/gl.h>
#endif
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#include <OpenGL/glext.h>
#endif
#ifdef __linux__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#define GL_GLES_PROTOTYPES 1
#include <GLES3/gl3.h>
#endif
#define __gl_h_ // block older GL
#include <glog/logging.h>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <array>
#include <fstream>
#include <string>
#include <folly/Format.h>
inline Eigen::Projective3f
frustum(float minX, float maxX, float minY, float maxY, float minZ, float maxZ = INFINITY) {
// see glFrustum: http://goo.gl/fsgoMf
float A = (maxX + minX) / (maxX - minX);
float B = (maxY + minY) / (maxY - minY);
float C = -(maxZ + minZ) / (maxZ - minZ);
float D = -2 * maxZ * minZ / (maxZ - minZ);
// fix special case maxZ == inf
if (std::isinf(maxZ)) {
C = -1;
D = -2 * minZ;
}
Eigen::Matrix4f m;
m << 2 * minZ / (maxX - minX), 0, A, 0, 0, 2 * minZ / (maxY - minY), B, 0, 0, 0, C, D, 0, 0, -1,
0;
return Eigen::Projective3f(m);
}
// produce a triangle strip covering a width x height grid
inline std::vector<GLuint> stripify(int width, int height, int skip = 1) {
std::vector<GLuint> result;
for (int y = 0; y + skip < height; y += skip) {
result.push_back(y * width); // double-hit the first index
for (int x = 0; x < width; x += skip) {
result.push_back(y * width + x);
result.push_back((y + skip) * width + x);
}
result.push_back(result.back()); // double-hit the last index
}
return result;
}
inline void attachShader(GLuint program, const GLint type, const std::string& source) {
GLuint shader = glCreateShader(type);
const char* p = source.c_str();
glShaderSource(shader, 1, &p, NULL);
glCompileShader(shader);
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (!status) {
GLint length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
std::vector<GLchar> log(length);
glGetShaderInfoLog(shader, length, &length, &log[0]);
LOG(FATAL) << folly::sformat("{}\nsource:\n{}", log.data(), source);
}
glAttachShader(program, shader);
glDeleteShader(shader); // ok: won't actually be deleted until detached
}
inline GLuint createProgram(const std::string& vs, const std::string& fs) {
GLuint program = glCreateProgram();
attachShader(program, GL_VERTEX_SHADER, vs);
attachShader(program, GL_FRAGMENT_SHADER, fs);
glLinkProgram(program);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (!status) {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
std::vector<GLchar> log(length);
glGetProgramInfoLog(program, length, &length, &log[0]);
LOG(FATAL) << folly::sformat("{}\nvs:\n{}\nfs\n{}", log.data(), vs, fs);
}
glUseProgram(program);
return program;
}
inline GLint getUniformLocation(GLuint program, const char* name) {
GLint result = glGetUniformLocation(program, name);
CHECK_NE(result, -1) << "can't find uniform '" << name << "'";
return result;
}
inline void setUniform(GLuint program, const char* name, const GLint& value) {
glUniform1i(getUniformLocation(program, name), value);
}
inline void setUniform(GLuint program, const char* name, const float& value) {
glUniform1f(getUniformLocation(program, name), value);
}
inline void setUniform(GLuint program, const char* name, const float& x, const float& y) {
glUniform2f(getUniformLocation(program, name), x, y);
}
// connect texture unit with target/texture and program/uniform[index]
inline void connectUnitWithTextureAndUniform(
const GLuint unit,
const GLenum target,
const GLuint texture,
const GLuint program,
const char* uniform,
int index = 0) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(target, texture);
glUniform1i(getUniformLocation(program, uniform) + index, unit);
}
inline void connectUnitWith2DTextureAndUniform(
const GLuint unit,
const GLuint texture,
const GLuint program,
const char* uniform,
int index = 0) {
connectUnitWithTextureAndUniform(unit, GL_TEXTURE_2D, texture, program, uniform, index);
}
inline GLint getAttribLocation(GLuint program, const char* name) {
GLint result = glGetAttribLocation(program, name);
CHECK_NE(result, -1) << "can't find attribute '" << name << "'";
return result;
}
template <typename T>
GLuint createBuffer(GLenum target, const T* p, size_t count) {
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(target, buffer);
glBufferData(target, count * sizeof(T), p, GL_STREAM_DRAW);
return buffer;
}
// should work for any v that is contiguous, has size() and operator[]
template <typename T>
GLuint createBuffer(GLenum target, const T& v) {
return createBuffer(target, &v[0], v.size());
}
inline GLenum getType(const uint8_t&) {
return GL_UNSIGNED_BYTE;
}
inline GLenum getType(const GLushort&) {
return GL_UNSIGNED_SHORT;
}
inline GLenum getType(const GLuint&) {
return GL_UNSIGNED_INT;
}
inline GLenum getType(const float&) {
return GL_FLOAT;
}
inline int getByteCount(GLenum type) {
switch (type) {
case GL_UNSIGNED_BYTE:
case GL_BYTE:
return 1;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
case GL_HALF_FLOAT:
return 2;
case GL_UNSIGNED_INT:
case GL_INT:
case GL_FLOAT:
return 4;
}
CHECK(false) << "unexpected type " << type;
}
inline int getChannelCount(GLenum format) {
switch (format) {
case GL_RED:
case GL_GREEN:
case GL_BLUE:
case GL_ALPHA:
return 1;
case GL_RG:
return 2;
case GL_RGB:
return 3;
case GL_RGBA:
return 4;
}
CHECK(false) << "unexpected format " << format;
}
// should work for any T which has size() and operator[]
template <typename T>
GLuint createVertexAttributes(GLuint location, const T* p, size_t count) {
GLuint buffer = createBuffer(GL_ARRAY_BUFFER, p, count);
glVertexAttribPointer(
location,
(GLint)(*p).size(), // dimension
getType((*p)[0]), // type
GL_TRUE, // normalized
0, // stride (0 means inferred)
(GLvoid*)0); // offset
glEnableVertexAttribArray(location);
return buffer;
}
template <typename T>
GLuint createVertexAttributes(GLuint location, const T& v) {
return createVertexAttributes(location, &v[0], v.size());
}
template <typename T>
GLuint createVertexAttributes(GLuint program, const char* name, const T& v) {
return createVertexAttributes(getAttribLocation(program, name), v);
}
template <typename T>
GLuint createVertexAttributes(GLuint program, const char* name, const T* p, size_t count) {
return createVertexAttributes(getAttribLocation(program, name), p, count);
}
template <typename T>
void drawElements(GLenum mode, uint32_t count) {
glDrawElements(mode, count, getType(T()), 0);
}
template <typename T>
void drawElements(GLenum mode) {
GLint bytes;
glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &bytes);
glDrawElements(mode, bytes / sizeof(T), getType(T()), 0);
}
// should work for any v which can be used in createBuffer()
template <typename T>
void drawElements(GLenum mode, const T& v) {
static_assert(std::is_same<decltype(v[0]), const GLuint&>::value, "TODO");
GLuint buffer = createBuffer(GL_ELEMENT_ARRAY_BUFFER, v);
glDrawElements(mode, v.size(), GL_UNSIGNED_INT, 0);
glDeleteBuffers(1, &buffer);
}
inline GLuint createVertexArray() {
GLuint vertexArray;
glGenVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
return vertexArray;
}
inline GLuint createFramebuffer(GLenum target = GL_FRAMEBUFFER) {
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(target, framebuffer);
return framebuffer;
}
inline GLuint createTexture(GLenum target = GL_TEXTURE_2D) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(target, texture);
return texture;
}
template <GLenum target = GL_TEXTURE_2D>
inline void setLinearFiltering(bool buildMipmaps = false) {
// Don't build mip maps unless asked
if (buildMipmaps) {
glGenerateMipmap(target);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
} else {
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
// this is the default already, but ...
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
template <GLenum target = GL_TEXTURE_2D>
inline void setTextureWrap(GLenum mode) {
glTexParameteri(target, GL_TEXTURE_WRAP_S, mode);
glTexParameteri(target, GL_TEXTURE_WRAP_T, mode);
}
// turn on anisotropic filtering, if available (0 means maximum supported)
template <GLenum target = GL_TEXTURE_2D>
inline void setTextureAniso(GLint aniso = 0) {
#ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT
if (aniso == 0) { // query the maximum value and set that
glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &aniso);
}
glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
#endif
}
inline GLuint createTexture(
const int width,
const int height,
const void* data,
const GLenum internalFormat, // e.g. GL_RGB8, GL_SRGB8_ALPHA8, GL_RGBA16F
const GLenum format, // e.g. GL_RED, GL_RGB, GL_BGRA
const GLenum type, // e.g. GL_UNSIGNED_BYTE, GL_FLOAT
const bool buildMipmaps = false) {
GLuint texId = createTexture();
glTexImage2D(
GL_TEXTURE_2D,
0, // level
internalFormat,
width,
height,
0, // border
format,
type,
data);
setLinearFiltering(buildMipmaps);
return texId;
}
inline GLuint createRenderbuffer(GLenum target = GL_RENDERBUFFER) {
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(target, renderbuffer);
return renderbuffer;
}
inline GLuint
createRenderbuffer(GLint width, GLint height, GLenum format, GLenum target = GL_RENDERBUFFER) {
GLuint renderbuffer = createRenderbuffer(target);
glRenderbufferStorage(target, format, width, height);
return renderbuffer;
}
inline GLuint createFramebufferTexture(int width, int height, GLenum format) {
GLuint texture = createTexture();
glTexImage2D(
GL_TEXTURE_2D,
0, // level
format,
width,
height,
0, // border must be 0
GL_RGBA,
GL_BYTE,
NULL); // no pixel data
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
return texture;
}
inline GLuint createFramebufferCubemapTexture(int width, int height, GLenum format) {
CHECK_EQ(width, height) << "cube faces must be square";
GLuint texture = createTexture(GL_TEXTURE_CUBE_MAP);
for (int face = 0; face < 6; ++face) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
0, // level
format,
width,
height,
0, // border must be 0
GL_RGBA,
GL_BYTE,
NULL); // no pixel data
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
return texture;
}
inline GLuint createFramebufferDepthTexture(int width, int height) {
GLuint depth = createTexture();
glTexImage2D(
GL_TEXTURE_2D,
0, // level
GL_DEPTH_COMPONENT,
width,
height,
0, // border must be 0
GL_DEPTH_COMPONENT,
GL_FLOAT,
NULL); // no pixel data
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0);
return depth;
}
inline GLuint createFramebufferColor(int width, int height, GLenum format = GL_RGB) {
GLuint color = createRenderbuffer();
glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color);
return color;
}
inline GLuint createFramebufferDepth(int width, int height) {
GLuint depth = createRenderbuffer();
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
return depth;
}
// useful when patching shaders: replace all occurrences of needle in haystack
inline void
replaceAll(std::string& haystack, const std::string& needle, const std::string& replacement) {
for (size_t pos; (pos = haystack.find(needle)) != std::string::npos;) {
haystack.replace(pos, needle.size(), replacement);
}
}
// a vertex shader suitable for use with fullscreen
inline std::string fullscreenVertexShader(
const char* attribute = "tex",
const char* varying = "texVar") {
std::string result = R"(
#version 330 core
in vec2 $ATTRIBUTE$;
out vec2 $VARYING$;
void main() {
gl_Position = vec4(2 * $ATTRIBUTE$ - 1, 0, 1);
$VARYING$ = $ATTRIBUTE$;
}
)";
replaceAll(result, "$ATTRIBUTE$", attribute);
replaceAll(result, "$VARYING$", varying);
return result;
}
// draw a fullscreen triangle passing (0, 0)..(1, 1) into 'attribute'
inline void fullscreen(const GLuint program, const char* attribute = "tex") {
GLuint vertexArray = createVertexArray();
std::array<std::array<float, 2>, 3> data{{{{0, 0}}, {{2, 0}}, {{0, 2}}}};
GLuint positions = createVertexAttributes(program, attribute, data);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDeleteBuffers(1, &positions);
glDeleteVertexArrays(1, &vertexArray);
}
inline std::vector<uint8_t> readPpmFile(int& width, int& height, const std::string& path) {
// read the metadata
std::ifstream file(path);
std::string magic;
file >> magic;
CHECK_EQ(magic, "P6");
file >> width;
file >> height;
int maxval;
file >> maxval;
CHECK_EQ(maxval, 255);
file.ignore();
// read the data
static const int kNumChannels = 3;
std::vector<uint8_t> rgb(width * height * kNumChannels);
file.read((char*)rgb.data(), rgb.size());
return rgb;
}
// Load a texture from a ppm file
inline GLuint loadTexture(const std::string& path, const bool buildMipmaps = false) {
int width;
int height;
std::vector<uint8_t> rgb = readPpmFile(width, height, path);
GLuint tex =
createTexture(width, height, rgb.data(), GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, buildMipmaps);
setTextureWrap(GL_CLAMP_TO_EDGE);
return tex;
}