summaryrefslogtreecommitdiff
path: root/src/gl
diff options
context:
space:
mode:
Diffstat (limited to 'src/gl')
-rw-r--r--src/gl/README16
-rw-r--r--src/gl/buffer.cc105
-rw-r--r--src/gl/buffer.h206
-rw-r--r--src/gl/draw.cc56
-rw-r--r--src/gl/draw.h217
-rw-r--r--src/gl/error.cc84
-rw-r--r--src/gl/error.h68
-rw-r--r--src/gl/init.cc95
-rw-r--r--src/gl/init.h86
-rw-r--r--src/gl/shader.cc204
-rw-r--r--src/gl/shader.h211
-rw-r--r--src/gl/texture.cc43
-rw-r--r--src/gl/texture.h259
-rw-r--r--src/gl/vertex_array.cc73
-rw-r--r--src/gl/vertex_array.h108
15 files changed, 1831 insertions, 0 deletions
diff --git a/src/gl/README b/src/gl/README
new file mode 100644
index 0000000..9e7aed4
--- /dev/null
+++ b/src/gl/README
@@ -0,0 +1,16 @@
+This directory defines an idiomatic C++ interface to parts of OpenGL.
+
+This library requires OpenGL 3.2 with the following extensions:
+ - ARB_texture_storage (or OpenGL >=4.2)
+ - ARB_direct_state_access (or OpenGL >=4.5)
+
+OpenGL is an inherently thread-hostile interface (it uses thread-local state).
+By default, this library will throw if it detects it is being used in a
+thread-unsafe way. However, this checking does carry a performance cost, so
+defining GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS disables these checks.
+
+By default, this library throws OpenGL errors as soon as they occur. This makes
+debugging easier, since it associates errors more closely with the operations to
+which they correspond. However, this bookkeeping carries a performance cost;
+defining GLPLANET_DISABLE_AGGRESSIVE_ERROR_CHECKING will reduce it to the
+absolute minimum required for correctness.
diff --git a/src/gl/buffer.cc b/src/gl/buffer.cc
new file mode 100644
index 0000000..a56e36a
--- /dev/null
+++ b/src/gl/buffer.cc
@@ -0,0 +1,105 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/buffer.h"
+
+#include <stdint.h>
+
+#include <stdexcept>
+
+#include "src/gl/error.h"
+#include "src/util.h"
+#include "third_party/abseil/absl/strings/substitute.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+namespace {
+
+constexpr uint16_t Pack(Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) noexcept {
+ static_assert(sizeof(Buffer::AccessFrequency) == 1);
+ static_assert(sizeof(Buffer::AccessNature) == 1);
+ return FromEnum(frequency) << 8 | FromEnum(nature);
+}
+
+GLenum CombineFrequencyAndNature(Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) {
+ using F = ::gl::Buffer::AccessFrequency;
+ using N = ::gl::Buffer::AccessNature;
+ switch (Pack(frequency, nature)) {
+ case Pack(F::kStream, N::kDraw):
+ return GL_STREAM_DRAW;
+ case Pack(F::kStream, N::kRead):
+ return GL_STREAM_READ;
+ case Pack(F::kStream, N::kCopy):
+ return GL_STREAM_COPY;
+
+ case Pack(F::kStatic, N::kDraw):
+ return GL_STATIC_DRAW;
+ case Pack(F::kStatic, N::kRead):
+ return GL_STATIC_READ;
+ case Pack(F::kStatic, N::kCopy):
+ return GL_STATIC_COPY;
+
+ case Pack(F::kDynamic, N::kDraw):
+ return GL_DYNAMIC_DRAW;
+ case Pack(F::kDynamic, N::kRead):
+ return GL_DYNAMIC_READ;
+ case Pack(F::kDynamic, N::kCopy):
+ return GL_DYNAMIC_COPY;
+
+ default:
+ throw std::invalid_argument(absl::Substitute(
+ "GL: invalid access frequency and/or nature ($0, $1)",
+ FromEnum(frequency), FromEnum(nature)));
+ }
+}
+
+} // namespace
+
+Buffer::Buffer(const Buffer& other)
+ : size_bytes_(other.size_bytes_),
+ frequency_(other.frequency_),
+ nature_(other.nature_) {
+ gl_internal::CheckThreadSafety();
+
+ glCreateBuffers(1, &buffer_);
+ gl_internal::UnnecessaryErrorCheck();
+
+ glNamedBufferData(buffer_, size_bytes_, /*data=*/nullptr,
+ CombineFrequencyAndNature(frequency_, nature_));
+ // An error check is necessary here to detect allocation failure, but it can
+ // be folded into a future error check.
+ gl_internal::UnnecessaryErrorCheck();
+
+ glCopyNamedBufferSubData(other.buffer_, buffer_, /*readOffset=*/0,
+ /*writeOffset=*/0, other.size_bytes_);
+ // This error check is necessary because other might be mapped.
+ gl_internal::ErrorCheck();
+
+ fprintf(stderr, "BUFFER COPY\n");
+}
+
+void Buffer::SetData(const void* data, int size_bytes,
+ AccessFrequency frequency, AccessNature nature) {
+ gl_internal::CheckThreadSafety();
+ glNamedBufferData(buffer_, size_bytes, data,
+ CombineFrequencyAndNature(frequency, nature));
+ // This error check is necessary to detect allocation failure.
+ gl_internal::ErrorCheck();
+ size_bytes_ = size_bytes;
+}
+
+} // namespace gl
diff --git a/src/gl/buffer.h b/src/gl/buffer.h
new file mode 100644
index 0000000..439533b
--- /dev/null
+++ b/src/gl/buffer.h
@@ -0,0 +1,206 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Buffer objects.
+
+#ifndef GLPLANET_SRC_GL_BUFFER_H_
+#define GLPLANET_SRC_GL_BUFFER_H_
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "src/gl/error.h"
+#include "third_party/abseil/absl/types/span.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+// A byte buffer in GPU memory.
+class Buffer final {
+ public:
+ // The frequency with which data in the buffer will be accessed.
+ enum class AccessFrequency : uint8_t { kStatic, kStream, kDynamic };
+
+ // The type of that access.
+ enum class AccessNature : uint8_t { kDraw, kRead, kCopy };
+
+ explicit Buffer() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking))
+ : size_bytes_(0),
+ frequency_(AccessFrequency::kStatic),
+ nature_(AccessNature::kDraw) {
+ gl_internal::CheckThreadSafety();
+ glCreateBuffers(1, &buffer_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ Buffer(const Buffer&);
+
+ Buffer& operator=(const Buffer& other) {
+ if (this != &other) {
+ Buffer other2(other);
+ swap(*this, other2);
+ }
+ return *this;
+ }
+
+ Buffer(Buffer&& other) noexcept
+ : buffer_(0),
+ size_bytes_(0),
+ frequency_(AccessFrequency::kStatic),
+ nature_(AccessNature::kDraw) {
+ *this = std::move(other);
+ }
+
+ Buffer& operator=(Buffer&& other) noexcept {
+ swap(*this, other);
+ return *this;
+ }
+
+ ~Buffer() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glDeleteBuffers(1, &buffer_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ friend void swap(Buffer& left, Buffer& right) noexcept {
+ using ::std::swap;
+ swap(left.buffer_, right.buffer_);
+ swap(left.size_bytes_, right.size_bytes_);
+ swap(left.frequency_, right.frequency_);
+ swap(left.nature_, right.nature_);
+ }
+
+ // Loads data into the buffer.
+ void SetData(const void*, int size_bytes, AccessFrequency, AccessNature);
+
+ int size() const noexcept { return size_bytes_; }
+
+ // The GL identifier for this buffer.
+ unsigned int id() const noexcept { return buffer_; }
+
+ private:
+ GLuint buffer_;
+ int size_bytes_;
+ AccessFrequency frequency_;
+ AccessNature nature_;
+};
+
+// A vertex buffer object.
+class VertexBuffer final {
+ public:
+ explicit VertexBuffer() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) =
+ default;
+
+ // A shorthand to construct the buffer and load data into it in one step.
+ template <typename T>
+ explicit VertexBuffer(absl::Span<const T> data,
+ Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) {
+ SetData(data, frequency, nature);
+ }
+
+ VertexBuffer(const VertexBuffer&) = default;
+ VertexBuffer& operator=(const VertexBuffer&) = default;
+ VertexBuffer(VertexBuffer&&) = default;
+ VertexBuffer& operator=(VertexBuffer&&) = default;
+
+ // Loads data into the buffer.
+ template <typename T>
+ void SetData(absl::Span<const T> data, Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) {
+ buffer_.SetData(data.data(), data.size() * sizeof(T), frequency, nature);
+ element_size_ = sizeof(T);
+ }
+
+ // The number of vertices in this buffer.
+ int size() const noexcept {
+ assert(buffer_.size() % element_size() == 0);
+ return buffer_.size() / element_size();
+ }
+
+ // The size, in bytes, of an individual vertex.
+ int element_size() const noexcept { return element_size_; }
+
+ // The GL identifier for this buffer.
+ unsigned int id() const noexcept { return buffer_.id(); }
+
+ private:
+ Buffer buffer_;
+ int element_size_;
+};
+
+// The types that can go in an element buffer.
+template <typename T>
+concept IsElement = std::same_as<T, uint8_t> || std::same_as<T, uint16_t> ||
+ std::same_as<T, uint32_t>;
+
+// An element buffer.
+class ElementBuffer final {
+ public:
+ explicit ElementBuffer() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) =
+ default;
+
+ // Constructs an element buffer containing the specified data.
+ template <IsElement T>
+ explicit ElementBuffer(absl::Span<const T> data,
+ Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) {
+ SetData(data, frequency, nature);
+ }
+
+ ElementBuffer(const ElementBuffer&) = default;
+ ElementBuffer& operator=(const ElementBuffer&) = default;
+ ElementBuffer(ElementBuffer&&) noexcept = default;
+ ElementBuffer& operator=(ElementBuffer&&) noexcept = default;
+
+ // Loads data into the buffer.
+ template <IsElement T>
+ void SetData(absl::Span<const T> data, Buffer::AccessFrequency frequency,
+ Buffer::AccessNature nature) {
+ buffer_.SetData(data.data(), data.size() * sizeof(T), frequency, nature);
+ element_size_ = sizeof(T);
+ }
+
+ // The number of elements in this buffer.
+ int size() const noexcept {
+ assert(buffer_.size() % element_size() == 0);
+ return buffer_.size() / element_size();
+ }
+
+ // The size, in bytes, of an individual element. This will always be 1, 2,
+ // or 4.
+ int element_size() const noexcept {
+ assert(element_size_ == 1 || element_size_ == 2 || element_size_ == 4);
+ return element_size_;
+ }
+
+ // The GL identifier for this buffer.
+ unsigned int id() const noexcept { return buffer_.id(); }
+
+ private:
+ Buffer buffer_;
+ int element_size_;
+};
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_BUFFER_H_
diff --git a/src/gl/draw.cc b/src/gl/draw.cc
new file mode 100644
index 0000000..9d20fe1
--- /dev/null
+++ b/src/gl/draw.cc
@@ -0,0 +1,56 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/draw.h"
+
+#include <assert.h>
+
+#include "src/gl/error.h"
+#include "src/gl/vertex_array.h"
+#include "src/util.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+void DrawElements(const gl::VertexArray& vao, Primitive mode) {
+ gl_internal::CheckThreadSafety();
+
+ GLenum type;
+ switch (vao.element_buffer().element_size()) {
+ case 1:
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case 2:
+ type = GL_UNSIGNED_SHORT;
+ break;
+ case 4:
+ type = GL_UNSIGNED_INT;
+ break;
+ default:
+ assert(false);
+ };
+
+ glDrawElements(FromEnum(mode), vao.element_buffer().size(), type,
+ /*indices=*/0);
+ // This error check is necessary because the mode could be unsupported.
+ gl_internal::ErrorCheck();
+}
+
+void SetViewport(int x, int y, int width, int height) {
+ gl_internal::CheckThreadSafety();
+ glViewport(x, y, width, height);
+ gl_internal::ErrorCheck();
+}
+
+} // namespace gl
diff --git a/src/gl/draw.h b/src/gl/draw.h
new file mode 100644
index 0000000..13deb43
--- /dev/null
+++ b/src/gl/draw.h
@@ -0,0 +1,217 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Drawing to the framebuffer.
+
+#ifndef GLPLANET_SRC_GL_DRAW_H_
+#define GLPLANET_SRC_GL_DRAW_H_
+
+#include <Eigen/Core>
+#include <initializer_list>
+#include <stdexcept>
+
+#include "src/gl/buffer.h"
+#include "src/gl/error.h"
+#include "src/gl/shader.h"
+#include "src/gl/texture.h"
+#include "src/gl/vertex_array.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+// Makes the specified shader program active.
+inline void SetActiveShaderProgram(ShaderProgram& program) {
+ gl_internal::CheckThreadSafety();
+ glUseProgram(program.id());
+ // This error check is necessary because glUseProgram can fail.
+ gl_internal::ErrorCheck();
+}
+
+// RAII version of the above: While live, makes the specified shader program
+// active.
+class ActiveShaderProgram final {
+ public:
+ explicit ActiveShaderProgram(ShaderProgram& program) {
+ gl_internal::CheckThreadSafety();
+ glGetIntegerv(GL_CURRENT_PROGRAM, &old_);
+ gl_internal::UnnecessaryErrorCheck();
+ SetActiveShaderProgram(program);
+ }
+
+ ActiveShaderProgram(const ActiveShaderProgram&) = delete;
+ ActiveShaderProgram& operator=(const ActiveShaderProgram&) = delete;
+
+ ~ActiveShaderProgram() {
+ gl_internal::CheckThreadSafety();
+ glUseProgram(old_);
+ // This error check is necessary because glUseProgram can fail.
+ gl_internal::ErrorCheck();
+ }
+
+ private:
+ GLint old_;
+};
+
+// Sets the specified uniform in the active shader. Throws std::logic_error in
+// the event of a type error (e.g., you set a mat4 uniform to a single integer).
+inline void SetActiveShaderUniform(int index, int v0) {
+ gl_internal::CheckThreadSafety();
+ glUniform1i(index, v0);
+ // This error check is necessary because the shader variable might have a
+ // different type than was passed in.
+ gl_internal::ErrorCheck();
+}
+inline void SetActiveShaderUniform(int index, const Eigen::Vector2f& value) {
+ gl_internal::CheckThreadSafety();
+ glUniform2fv(index, /*count=*/1, value.data());
+ // This error check is necessary because the shader variable might have a
+ // different type than was passed in.
+ gl_internal::ErrorCheck();
+}
+inline void SetActiveShaderUniform(int index, const Eigen::Vector3f& value) {
+ gl_internal::CheckThreadSafety();
+ glUniform3fv(index, /*count=*/1, value.data());
+ // This error check is necessary because the shader variable might have a
+ // different type than was passed in.
+ gl_internal::ErrorCheck();
+}
+inline void SetActiveShaderUniform(int index, const Eigen::Matrix4f& value) {
+ gl_internal::CheckThreadSafety();
+ glUniformMatrix4fv(index, /*count=*/1, /*transpose=*/GL_FALSE, value.data());
+ // This error check is necessary because the shader variable might have a
+ // different type than was passed in.
+ gl_internal::ErrorCheck();
+}
+
+// Binds the specified VAO.
+inline void BindVertexArray(const VertexArray& vao) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glBindVertexArray(vao.id());
+ gl_internal::UnnecessaryErrorCheck();
+}
+
+// RAII version of the above: While live, binds the specified VAO.
+class BoundVertexArray final {
+ public:
+ explicit BoundVertexArray(const VertexArray& vao) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_);
+ gl_internal::UnnecessaryErrorCheck();
+ BindVertexArray(vao);
+ }
+
+ BoundVertexArray(const BoundVertexArray&) = delete;
+ BoundVertexArray& operator=(const BoundVertexArray&) = delete;
+
+ ~BoundVertexArray() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glBindVertexArray(old_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ private:
+ GLint old_;
+};
+
+// Sets the current texture unit.
+inline void UseTextureUnit(int unit) {
+ gl_internal::CheckThreadSafety();
+ glActiveTexture(GL_TEXTURE0 + unit);
+ // This error check is necessary because the user might have passed in a
+ // too-large index for the texture unit.
+ gl_internal::ErrorCheck();
+}
+
+// Binds the specified 2D texture.
+inline void BindTexture2d(const Texture2d& tex) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glBindTexture(GL_TEXTURE_2D, tex.id());
+ gl_internal::UnnecessaryErrorCheck();
+}
+
+// RAII version of the above: While live, binds the specified 2D texture.
+class BoundTexture2d final {
+ public:
+ explicit BoundTexture2d(const Texture2d& tex) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_);
+ gl_internal::UnnecessaryErrorCheck();
+ BindTexture2d(tex);
+ }
+
+ BoundTexture2d(const BoundTexture2d&) = delete;
+ BoundTexture2d& operator=(const BoundTexture2d&) = delete;
+
+ ~BoundTexture2d() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glBindTexture(GL_TEXTURE_2D, old_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ private:
+ GLint old_;
+};
+
+enum class GlBuffer : GLenum {
+ kColor = GL_COLOR_BUFFER_BIT,
+ kDepth = GL_DEPTH_BUFFER_BIT,
+ kStencil = GL_STENCIL_BUFFER_BIT,
+};
+
+inline void Clear(const std::initializer_list<GlBuffer>& buffers) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+
+ GLenum mask = 0;
+ for (GlBuffer b : buffers) {
+ mask |= FromEnum(b);
+ }
+ glClear(mask);
+ gl_internal::UnnecessaryErrorCheck();
+}
+
+enum class Primitive : GLenum {
+ kPoints = GL_POINTS,
+ kLineStrip = GL_LINE_STRIP,
+ kLineLoop = GL_LINE_LOOP,
+ kLines = GL_LINES,
+ kLineStripAdjacency = GL_LINE_STRIP_ADJACENCY,
+ kLinesAdjacency = GL_LINES_ADJACENCY,
+ kTriangleStrip = GL_TRIANGLE_STRIP,
+ kTriangleFan = GL_TRIANGLE_FAN,
+ kTriangles = GL_TRIANGLES,
+ kTriangleStripAdjacency = GL_TRIANGLE_STRIP_ADJACENCY,
+ kTrianglesAdjacency = GL_TRIANGLES_ADJACENCY,
+ kPatches = GL_PATCHES,
+};
+
+// Draws the elements from the specified VAO. The VAO must already be bound.
+void DrawElements(const gl::VertexArray&, Primitive);
+
+void SetViewport(int x, int y, int width, int height);
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_DRAW_H_
diff --git a/src/gl/error.cc b/src/gl/error.cc
new file mode 100644
index 0000000..20b2c74
--- /dev/null
+++ b/src/gl/error.cc
@@ -0,0 +1,84 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/error.h"
+
+#include <new>
+#include <stdexcept>
+#include <thread>
+
+#include "third_party/glew/include/GL/glew.h"
+#include "third_party/abseil/absl/base/const_init.h"
+#include "third_party/abseil/absl/synchronization/mutex.h"
+#include "third_party/abseil/absl/types/optional.h"
+#include "third_party/abseil/absl/meta/type_traits.h"
+
+namespace gl_internal {
+
+#ifndef GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS
+
+namespace {
+
+ABSL_CONST_INIT absl::Mutex thread_id_mu(absl::kConstInit);
+ABSL_CONST_INIT absl::optional<std::thread::id> thread_id;
+static_assert(absl::is_trivially_destructible<decltype(thread_id)>::value);
+
+} // namespace
+
+void RecordThreadImpl() {
+ absl::MutexLock lock(&thread_id_mu);
+ if (thread_id.has_value()) {
+ throw std::logic_error("GL: gl_internal::RecordThread called twice");
+ }
+ thread_id = std::this_thread::get_id();
+}
+
+void CheckThreadSafetyImpl() {
+ absl::MutexLock lock(&thread_id_mu);
+ if (!thread_id.has_value()) {
+ throw std::logic_error(
+ "GL: gl_internal::CheckThreadSafety called before "
+ "gl_internal::RecordThread");
+ }
+ if (std::this_thread::get_id() != *thread_id) {
+ throw std::logic_error("GL: detected access from multiple threads");
+ }
+}
+
+#endif // !defined(GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS)
+
+void ErrorCheck() {
+ switch (glGetError()) {
+ case GL_NO_ERROR:
+ return;
+ case GL_INVALID_ENUM:
+ throw std::invalid_argument(
+ "GL: unacceptable value specified for enumerated argument");
+ case GL_INVALID_VALUE:
+ throw std::out_of_range("GL: numeric argument out of range");
+ case GL_INVALID_OPERATION:
+ throw std::logic_error(
+ "GL: specified operation not allowed in current state");
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ throw std::logic_error("GL: framebuffer object is not complete");
+ case GL_OUT_OF_MEMORY:
+ throw std::bad_alloc();
+ case GL_STACK_UNDERFLOW:
+ throw std::runtime_error("GL: stack underflow");
+ case GL_STACK_OVERFLOW:
+ throw std::runtime_error("GL: stack overflow");
+ }
+}
+
+} // namespace gl_internal
diff --git a/src/gl/error.h b/src/gl/error.h
new file mode 100644
index 0000000..a56bae9
--- /dev/null
+++ b/src/gl/error.h
@@ -0,0 +1,68 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Error handling and thread-safety checking.
+
+#ifndef GLPLANET_SRC_GL_ERROR_H_
+#define GLPLANET_SRC_GL_ERROR_H_
+
+namespace gl_internal {
+
+#ifdef GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS
+constexpr bool kThreadSafetyChecks = false;
+#else
+constexpr bool kThreadSafetyChecks = true;
+#endif
+
+#ifdef GLPLANET_DISABLE_AGGRESSIVE_ERROR_CHECKING
+constexpr bool kAggressiveErrorChecking = false;
+#else
+constexpr bool kAggressiveErrorChecking = true;
+#endif
+
+#ifndef GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS
+
+void RecordThreadImpl();
+
+void CheckThreadSafetyImpl();
+
+#endif
+
+// Records the calling thread as the OpenGL thread.
+inline void RecordThread() noexcept(!kThreadSafetyChecks) {
+#ifndef GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS
+ RecordThreadImpl();
+#endif
+}
+
+// Throws std::logic_error if the current thread is not the OpenGL thread.
+inline void CheckThreadSafety() noexcept(!kThreadSafetyChecks) {
+#ifndef GLPLANET_DISABLE_GL_THREAD_SAFETY_CHECKS
+ CheckThreadSafetyImpl();
+#endif
+}
+
+// Checks the GL state for errors and throws if an error is present.
+void ErrorCheck();
+
+// As ErrorCheck, but disabled by GLPLANET_DISABLE_AGGRESSIVE_ERROR_CHECKING.
+inline void UnnecessaryErrorCheck() noexcept(!kAggressiveErrorChecking) {
+ if constexpr (kAggressiveErrorChecking) {
+ ErrorCheck();
+ }
+}
+
+} // namespace gl_internal
+
+#endif // GLPLANET_SRC_GL_ERROR_H_
diff --git a/src/gl/init.cc b/src/gl/init.cc
new file mode 100644
index 0000000..b8ac83e
--- /dev/null
+++ b/src/gl/init.cc
@@ -0,0 +1,95 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/init.h"
+
+#include <stdexcept>
+
+#include "src/gl/error.h"
+#include "third_party/abseil/absl/strings/str_cat.h"
+#include "third_party/abseil/absl/strings/substitute.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+namespace {
+
+using ::gl_internal::ErrorCheck;
+using ::gl_internal::UnnecessaryErrorCheck;
+
+void InitializeGlew() {
+ glewExperimental = GL_TRUE;
+ if (GLenum err = glewInit(); err != GLEW_OK) {
+ const GLubyte* error_string = glewGetErrorString(err);
+ throw std::runtime_error(absl::StrCat(
+ "GL: glewInit failed: ", reinterpret_cast<const char*>(error_string)));
+ }
+}
+
+void CheckGlVersion() {
+ GLint major_version;
+ try {
+ glGetIntegerv(GL_MAJOR_VERSION, &major_version);
+ ErrorCheck();
+ } catch (const std::invalid_argument&) {
+ // GL_MAJOR_VERSION doesn't even exist yet, which means we're on OpenGL 2.
+ throw std::runtime_error("GL: OpenGL 3.2 is required");
+ }
+ if (major_version == 3) {
+ GLint minor_version;
+ glGetIntegerv(GL_MINOR_VERSION, &minor_version);
+ UnnecessaryErrorCheck();
+ if (minor_version < 2) {
+ throw std::runtime_error("GL: OpenGL 3.2 is required");
+ }
+ }
+}
+
+void CheckGlExtensions() {
+ if (!GLEW_ARB_texture_storage) {
+ throw std::runtime_error("GL: ARB_texture_storage is required");
+ }
+ if (!GLEW_ARB_direct_state_access) {
+ throw std::runtime_error("GL: ARB_direct_state_access is required");
+ }
+}
+
+} // namespace
+
+void InitializeForThisThread() {
+ gl_internal::RecordThread();
+ InitializeGlew();
+ ErrorCheck(); // just to make sure we're starting clean
+ CheckGlVersion();
+ CheckGlExtensions();
+
+ // Assume byte-aligned rows in images.
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ UnnecessaryErrorCheck();
+}
+
+void SetClearColor(float red, float green, float blue, float alpha) {
+ gl_internal::CheckThreadSafety();
+
+ if (red < 0 || red > 1 || green < 0 || green > 1 || blue < 0 || blue > 1 ||
+ alpha < 0 || alpha > 1) {
+ throw std::invalid_argument(absl::Substitute(
+ "GL: invalid clear color ($0, $1, $2, $3)", red, green, blue, alpha));
+ }
+
+ glClearColor(red, green, blue, alpha);
+ gl_internal::UnnecessaryErrorCheck();
+}
+
+} // namespace gl
diff --git a/src/gl/init.h b/src/gl/init.h
new file mode 100644
index 0000000..ad58ac6
--- /dev/null
+++ b/src/gl/init.h
@@ -0,0 +1,86 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// OpenGL initialization.
+
+#ifndef GLPLANET_SRC_GL_INIT_H_
+#define GLPLANET_SRC_GL_INIT_H_
+
+#include "src/gl/error.h"
+#include "src/util.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+// OpenGL capabilities
+enum class Capability : GLenum {
+ kBlend = GL_BLEND,
+ kClipDistance0 = GL_CLIP_DISTANCE0,
+ kClipDistance1 = GL_CLIP_DISTANCE1,
+ kClipDistance2 = GL_CLIP_DISTANCE2,
+ kClipDistance3 = GL_CLIP_DISTANCE3,
+ kClipDistance4 = GL_CLIP_DISTANCE4,
+ kClipDistance5 = GL_CLIP_DISTANCE5,
+ kColorLogicOp = GL_COLOR_LOGIC_OP,
+ kCullFace = GL_CULL_FACE,
+ kDepthClamp = GL_DEPTH_CLAMP,
+ kDepthTest = GL_DEPTH_TEST,
+ kDither = GL_DITHER,
+ kFramebufferSrgb = GL_FRAMEBUFFER_SRGB,
+ kLineSmooth = GL_LINE_SMOOTH,
+ kMultisample = GL_MULTISAMPLE,
+ kPolygonOffsetFill = GL_POLYGON_OFFSET_FILL,
+ kPolygonOffsetLine = GL_POLYGON_OFFSET_LINE,
+ kPolygonOffsetPoint = GL_POLYGON_OFFSET_POINT,
+ kPolygonSmooth = GL_POLYGON_SMOOTH,
+ kRasterizerDiscard = GL_RASTERIZER_DISCARD,
+ kSampleAlphaToCoverage = GL_SAMPLE_ALPHA_TO_COVERAGE,
+ kSampleAlphaToOne = GL_SAMPLE_ALPHA_TO_ONE,
+ kSampleCoverage = GL_SAMPLE_COVERAGE,
+ kSampleShading = GL_SAMPLE_SHADING,
+ kSampleMask = GL_SAMPLE_MASK,
+ kScissorTest = GL_SCISSOR_TEST,
+ kStencilTest = GL_STENCIL_TEST,
+ kProgramPointSize = GL_PROGRAM_POINT_SIZE,
+
+ // Added in GL 3.1
+ kPrimitiveRestart = GL_PRIMITIVE_RESTART,
+
+ // Added in GL 3.2
+ kTextureCubeMapSeamless = GL_TEXTURE_CUBE_MAP_SEAMLESS,
+};
+
+// Initializes the OpenGL state machine in the current thread. The current
+// thread must have an OpenGL context bound.
+//
+// You must call this function exactly once in your program. Once you do so, you
+// may not call any other function in this namespace from any other thread.
+void InitializeForThisThread();
+
+// Enables the specified OpenGL capability.
+inline void Enable(Capability cap) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glEnable(FromEnum(cap));
+ gl_internal::UnnecessaryErrorCheck();
+}
+
+// Sets the clear color for the active framebuffer. Color coordinates should be
+// in the range [0, 1].
+void SetClearColor(float red, float green, float blue, float alpha);
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_INIT_H_
diff --git a/src/gl/shader.cc b/src/gl/shader.cc
new file mode 100644
index 0000000..77887f2
--- /dev/null
+++ b/src/gl/shader.cc
@@ -0,0 +1,204 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/shader.h"
+
+#include <stdexcept>
+#include <string>
+
+#include "src/gl/error.h"
+#include "third_party/abseil/absl/strings/str_cat.h"
+#include "third_party/abseil/absl/strings/string_view.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+namespace {
+
+using ::gl_internal::CheckThreadSafety;
+using ::gl_internal::ErrorCheck;
+using ::gl_internal::UnnecessaryErrorCheck;
+
+std::string GetInfoLog(
+ void (*gl_get)(GLuint, GLenum, GLint*),
+ void (*gl_get_info_log)(GLuint, GLsizei, GLsizei*, GLchar*),
+ GLuint resource) noexcept(!gl_internal::kAggressiveErrorChecking) {
+ GLint log_length;
+ gl_get(resource, GL_INFO_LOG_LENGTH, &log_length);
+ UnnecessaryErrorCheck();
+ if (log_length == 0) {
+ return "";
+ }
+
+ std::string log(log_length, '\0');
+ gl_get_info_log(resource, log_length, /*length=*/nullptr, log.data());
+ UnnecessaryErrorCheck();
+ log.pop_back(); // pop the nullptr
+ return log;
+}
+
+} // namespace
+
+Shader::Shader(GLenum type) : source_set_(false), compile_attempted_(false) {
+ CheckThreadSafety();
+
+ shader_ = glCreateShader(type);
+ UnnecessaryErrorCheck();
+ if (shader_ == 0) {
+ throw std::runtime_error("GL: failed to create shader");
+ }
+}
+
+void Shader::SetSource(absl::string_view source) {
+ CheckThreadSafety();
+
+ std::array<const GLchar*, 1> source_ptrs = {source.data()};
+ if (source.size() > std::numeric_limits<GLint>::max()) {
+ throw std::invalid_argument(
+ "GL: shader source is too large for OpenGL to handle");
+ }
+ std::array<GLint, source_ptrs.size()> lengths = {
+ static_cast<GLint>(source.size())};
+ glShaderSource(shader_, source_ptrs.size(), source_ptrs.data(),
+ lengths.data());
+ UnnecessaryErrorCheck();
+
+ source_set_ = true;
+ compile_attempted_ = false;
+}
+
+void Shader::Compile() {
+ CheckThreadSafety();
+
+ if (!source_set_) {
+ throw std::logic_error(
+ "GL: shader compilation requested, but no source available");
+ }
+
+ glCompileShader(shader_);
+ UnnecessaryErrorCheck();
+
+ compile_attempted_ = true;
+ compile_log_ = GetInfoLog(glGetShaderiv, glGetShaderInfoLog, shader_);
+
+ GLint compiled;
+ glGetShaderiv(shader_, GL_COMPILE_STATUS, &compiled);
+ UnnecessaryErrorCheck();
+ if (!compiled) {
+ throw std::runtime_error(
+ absl::StrCat("GL: failed to compile shader: ", compile_log_));
+ }
+}
+
+const std::string& Shader::compile_log() const {
+ if (!compile_attempted_) {
+ throw std::logic_error(
+ "GL: compilation log examined before shader was compiled");
+ }
+ return compile_log_;
+}
+
+ShaderProgram::ShaderProgram()
+ : has_fragment_shader_(false), link_attempted_(false) {
+ CheckThreadSafety();
+ program_ = glCreateProgram();
+ if (program_ == 0) {
+ throw std::runtime_error("GL: failed to create shader program");
+ }
+}
+
+void ShaderProgram::SetFragmentDataLocation(const char* name,
+ int framebuffer_id) {
+ CheckThreadSafety();
+
+ if (name == nullptr) {
+ throw std::invalid_argument(
+ "GL: requested fragment shader uniform with null name");
+ }
+ if (!has_fragment_shader_) {
+ throw std::logic_error(absl::StrCat(
+ "GL: cannot set data location for fragment shader uniform ", name,
+ ": no fragment shader present"));
+ }
+
+ glBindFragDataLocation(program_, framebuffer_id, name);
+ // This error check is be necessary because the name might be reserved.
+ ErrorCheck();
+}
+
+void ShaderProgram::Link() {
+ CheckThreadSafety();
+
+ glLinkProgram(program_);
+ UnnecessaryErrorCheck();
+
+ link_attempted_ = true;
+ link_log_ = GetInfoLog(glGetProgramiv, glGetProgramInfoLog, program_);
+
+ GLint linked;
+ glGetProgramiv(program_, GL_LINK_STATUS, &linked);
+ UnnecessaryErrorCheck();
+ if (!linked) {
+ throw std::runtime_error(
+ absl::StrCat("GL: failed to link shader program: ", link_log_));
+ }
+}
+
+const std::string& ShaderProgram::link_log() const {
+ if (!link_attempted_) {
+ throw std::logic_error(
+ "GL: link log examined before shader program was linked");
+ }
+ return link_log_;
+}
+
+int ShaderProgram::active_vertex_attribute(const char* name) const {
+ CheckThreadSafety();
+
+ if (name == nullptr) {
+ throw std::invalid_argument(
+ "GL: requested active vertex attribute with null name");
+ }
+
+ int index = glGetAttribLocation(program_, name);
+ // This error check is necessary because the program might not have been
+ // linked yet.
+ ErrorCheck();
+
+ if (index == -1) {
+ throw std::invalid_argument(absl::StrCat("GL: requested vertex attribute ",
+ name, " is reserved or inactive"));
+ }
+ return index;
+}
+
+int ShaderProgram::active_uniform(const char* name) const {
+ CheckThreadSafety();
+
+ if (name == nullptr) {
+ throw std::invalid_argument("GL: requested active uniform with null name");
+ }
+
+ int index = glGetUniformLocation(program_, name);
+ // This error check is necessary because the program might not have been
+ // linked yet.
+ ErrorCheck();
+ if (index == -1) {
+ throw std::invalid_argument(absl::StrCat("GL: requested uniform ", name,
+ " is reserved or inactive"));
+ }
+ return index;
+}
+
+} // namespace gl
diff --git a/src/gl/shader.h b/src/gl/shader.h
new file mode 100644
index 0000000..b4cfb63
--- /dev/null
+++ b/src/gl/shader.h
@@ -0,0 +1,211 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Shaders and shader programs.
+
+#ifndef GLPLANET_SRC_GL_SHADER_H_
+#define GLPLANET_SRC_GL_SHADER_H_
+
+#include <array>
+#include <string>
+
+#include "src/gl/error.h"
+#include "third_party/abseil/absl/strings/string_view.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+class FragmentShader;
+class VertexShader;
+
+// An individual shader.
+//
+// The shader life cycle looks like this:
+//
+// Shader s;
+// s.SetSource(source_location);
+// s.Compile();
+// std::cerr << s.compile_log();
+//
+// The Compile function throws if errors occur during compilation, but warnings
+// are merely saved. You must retrieve the compilation log if you wish to report
+// warnings.
+class Shader {
+ public:
+ Shader(Shader&&) noexcept = default;
+ Shader& operator=(Shader&&) noexcept = default;
+
+ virtual ~Shader() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glDeleteShader(shader_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ // Loads the shader source. This function copies the source buffer, so the
+ // source buffer need not stay valid after this call.
+ //
+ // You may call this function repeatedly. Doing so invalidates the shader, and
+ // you must recompile it with Compile before using it.
+ void SetSource(absl::string_view source);
+
+ // Compiles the shader. Throws a std::runtime_error including the compilation
+ // log if compilation fails. If compilation succeeds but generates warnings,
+ // this function silently returns; check the compile_log member to see what
+ // the warnings were.
+ //
+ // You must call SetSource before calling this function.
+ //
+ // You may call this function repeatedly.
+ void Compile();
+
+ // Fetches the shader compilation log. If this is empty, there were no
+ // warnings during the compilation.
+ //
+ // You must call Compile before calling this function.
+ //
+ // You may call this function repeatedly.
+ const std::string& compile_log() const;
+
+ // The GL identifier for this shader.
+ unsigned int id() const noexcept { return shader_; }
+
+ protected:
+ GLuint shader_;
+ bool source_set_;
+ bool compile_attempted_;
+ std::string compile_log_;
+
+ private:
+ friend class FragmentShader;
+ friend class VertexShader;
+
+ explicit Shader(GLenum type);
+};
+
+// A fragment shader.
+class FragmentShader final : public Shader {
+ public:
+ explicit FragmentShader() : Shader(GL_FRAGMENT_SHADER) {}
+};
+
+// A vertex shader.
+class VertexShader final : public Shader {
+ public:
+ explicit VertexShader() : Shader(GL_VERTEX_SHADER) {}
+};
+
+// A shader program consisting of multiple shaders.
+//
+// The shader program life cycle looks like this:
+//
+// ShaderProgram p;
+// p.attach(s);
+// p.SetFragmentDataLocation("output_color", framebuffer_id);
+// p.Link();
+// std::cerr << s.link_log();
+// DoSomethingWith(p.active_vertex_attribute("whatever"));
+//
+// Analogously to Shader::Compile, Link throws if errors occur during linking,
+// but warnings are merely saved. You must retrieve the link log if you wish to
+// report warnings.
+class ShaderProgram final {
+ public:
+ explicit ShaderProgram();
+
+ ShaderProgram(ShaderProgram&&) noexcept = default;
+ ShaderProgram& operator=(ShaderProgram&&) noexcept = default;
+
+ ~ShaderProgram() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glDeleteProgram(program_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ // Attach shaders to the program. The shaders need not be compiled, but you
+ // must compile them before calling Link.
+ //
+ // You may call these functions repeatedly, so long as you never pass the same
+ // shader twice. In any case, calling these functions invalidates the program,
+ // and you must relink it with Link before using it.
+ void Attach(const VertexShader& shader) { AttachShader(shader); }
+ void Attach(const FragmentShader& shader) {
+ AttachShader(shader);
+ has_fragment_shader_ = true;
+ }
+
+ // Associates a fragment shader output variable with an output framebuffer.
+ // The variable name must not start with "gl_". The default framebuffer has ID
+ // 0.
+ //
+ // You must have a fragment shader attached to this program to call this
+ // function.
+ //
+ // You may call this function repeatedly. Doing so invalidates the program,
+ // and you must relink it with Link before using it.
+ void SetFragmentDataLocation(const char* name, int framebuffer_id);
+
+ // Links the shader program. Throws a std::runtime_error including the link
+ // log if linking fails. If linking succeeds but generates warnings, this
+ // function silently returns; check the link_log member to see what the
+ // warnings were.
+ //
+ // You may call this function repeatedly.
+ void Link();
+
+ // Fetches the program link log. If this is empty, there were no warnings
+ // during the link.
+ //
+ // You must call Link before calling this function.
+ //
+ // You may call this function repeatedly.
+ const std::string& link_log() const;
+
+ // The index of the specified active vertex attribute. The variable name must
+ // not start with "gl_".
+ //
+ // Throws std::invalid_argument if the specified name is reserved, inactive,
+ // or nonexistent. Throws std::logic_error if the program has not been linked.
+ int active_vertex_attribute(const char* name) const;
+
+ // The index of the specified active uniform variable. The variable name must
+ // not start with "gl_".
+ //
+ // Throws std::invalid_argument if the specified name is reserved, inactive,
+ // or nonexistent. Throws std::logic_error if the program has not been linked.
+ int active_uniform(const char* name) const;
+
+ // The GL identifier for this program.
+ unsigned int id() const noexcept { return program_; }
+
+ private:
+ void AttachShader(const Shader& shader) {
+ gl_internal::CheckThreadSafety();
+ glAttachShader(program_, shader.id());
+ // This error check is necessary because the user might have passed in the
+ // same shader twice.
+ gl_internal::ErrorCheck();
+ link_attempted_ = false;
+ }
+
+ GLuint program_;
+ bool has_fragment_shader_;
+ bool link_attempted_;
+ std::string link_log_;
+};
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_SHADER_H_
diff --git a/src/gl/texture.cc b/src/gl/texture.cc
new file mode 100644
index 0000000..cd877c9
--- /dev/null
+++ b/src/gl/texture.cc
@@ -0,0 +1,43 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/texture.h"
+
+#include <stdexcept>
+
+#include "src/gl/error.h"
+#include "src/util.h"
+#include "third_party/abseil/absl/strings/str_cat.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+void Texture2d::LoadSubimage(int width, int height, PixelFormat format,
+ PixelType type, const void* pixels, int level) {
+ gl_internal::CheckThreadSafety();
+
+ if (level >= levels_) {
+ throw std::invalid_argument(
+ absl::StrCat("GL: invalid texture subimage level $0; maximum is $1",
+ level, levels_ - 1));
+ }
+
+ glTextureSubImage2D(texture_, level, /*xoffset=*/0, /*yoffset=*/0, width,
+ height, FromEnum(format), FromEnum(type), pixels);
+ // This error check is necessary for a variety of reasons, including a
+ // type/format mismatch.
+ gl_internal::ErrorCheck();
+}
+
+} // namespace gl
diff --git a/src/gl/texture.h b/src/gl/texture.h
new file mode 100644
index 0000000..17541e0
--- /dev/null
+++ b/src/gl/texture.h
@@ -0,0 +1,259 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Texture objects.
+
+#ifndef GLPLANET_SRC_GL_TEXTURE_H_
+#define GLPLANET_SRC_GL_TEXTURE_H_
+
+#include "src/gl/error.h"
+#include "src/util.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+class Texture2d;
+
+// A generic texture. You can't instantiate this directly; instead, instantiate
+// one of its derived classes with an explicit dimensionality.
+class Texture {
+ public:
+ // Options for the texture's sample format.
+ enum class Format : GLenum {
+ kR8 = GL_R8,
+ kR8Snorm = GL_R8_SNORM,
+ kR16 = GL_R16,
+ kR16Snorm = GL_R16_SNORM,
+ kRg8 = GL_RG8,
+ kRg8Snorm = GL_RG8_SNORM,
+ kRg16 = GL_RG16,
+ kRg16Snorm = GL_RG16_SNORM,
+ kR3G3B2 = GL_R3_G3_B2,
+ kRgb4 = GL_RGB4,
+ kRgb5 = GL_RGB5,
+ kRgb8 = GL_RGB8,
+ kRgb8Snorm = GL_RGB8_SNORM,
+ kRgb10 = GL_RGB10,
+ kRgb12 = GL_RGB12,
+ kRgb16Snorm = GL_RGB16_SNORM,
+ kRgba2 = GL_RGBA2,
+ kRgba4 = GL_RGBA4,
+ kRgb5A1 = GL_RGB5_A1,
+ kRgba8 = GL_RGBA8,
+ kRgba8Snorm = GL_RGBA8_SNORM,
+ kRgb10A2 = GL_RGB10_A2,
+ kRgb10A2Ui = GL_RGB10_A2UI,
+ kRgba12 = GL_RGBA12,
+ kRgba16 = GL_RGBA16,
+ kSrgb8 = GL_SRGB8,
+ kSrgb8Alpha8 = GL_SRGB8_ALPHA8,
+ kR16f = GL_R16F,
+ kRg16f = GL_RG16F,
+ kRgb16f = GL_RGB16F,
+ kRgba16f = GL_RGBA16F,
+ kR32f = GL_R32F,
+ kRg32f = GL_RG32F,
+ kRgb32f = GL_RGB32F,
+ kRgba32f = GL_RGBA32F,
+ kR11fG11fB10f = GL_R11F_G11F_B10F,
+ kRgb9E5 = GL_RGB9_E5,
+ kR8i = GL_R8I,
+ kR8ui = GL_R8UI,
+ kR16i = GL_R16I,
+ kR16ui = GL_R16UI,
+ kR32i = GL_R32I,
+ kR32ui = GL_R32UI,
+ kRg8i = GL_RG8I,
+ kRg8ui = GL_RG8UI,
+ kRg16i = GL_RG16I,
+ kRg16ui = GL_RG16UI,
+ kRg32i = GL_RG32I,
+ kRg32ui = GL_RG32UI,
+ kRgb8i = GL_RGB8I,
+ kRgb8ui = GL_RGB8UI,
+ kRgb16i = GL_RGB16I,
+ kRgb16ui = GL_RGB16UI,
+ kRgb32i = GL_RGB32I,
+ kRgb32ui = GL_RGB32UI,
+ kRgba8i = GL_RGBA8I,
+ kRgba8ui = GL_RGBA8UI,
+ kRgba16i = GL_RGBA16I,
+ kRgba16ui = GL_RGBA16UI,
+ kRgba32i = GL_RGBA32I,
+ kRgba32ui = GL_RGBA32UI,
+ };
+
+ // Pixel order.
+ enum class PixelFormat : GLenum {
+ kRed = GL_RED,
+ kRg = GL_RG,
+ kRgb = GL_RGB,
+ kBgr = GL_BGR,
+ kRgba = GL_RGBA,
+ kBgra = GL_BGRA,
+ };
+
+ // Bits per pixel and subpixel.
+ enum class PixelType : GLenum {
+ kUnsignedByte = GL_UNSIGNED_BYTE,
+ kByte = GL_BYTE,
+ kUnsignedShort = GL_UNSIGNED_SHORT,
+ kShort = GL_SHORT,
+ kUnsignedInt = GL_UNSIGNED_INT,
+ kInt = GL_INT,
+ kFloat = GL_FLOAT,
+ kUnsignedByte332 = GL_UNSIGNED_BYTE_3_3_2,
+ kUnsignedByte233Rev = GL_UNSIGNED_BYTE_2_3_3_REV,
+ kUnsignedShort565 = GL_UNSIGNED_SHORT_5_6_5,
+ kUnsignedShort565Rev = GL_UNSIGNED_SHORT_5_6_5_REV,
+ kUnsignedShort4444 = GL_UNSIGNED_SHORT_4_4_4_4,
+ kUnsignedShort4444Rev = GL_UNSIGNED_SHORT_4_4_4_4_REV,
+ kUnsignedShort5551 = GL_UNSIGNED_SHORT_5_5_5_1,
+ kUnsignedShort1555Rev = GL_UNSIGNED_SHORT_1_5_5_5_REV,
+ kUnsignedInt8888 = GL_UNSIGNED_INT_8_8_8_8,
+ kUnsignedInt8888Rev = GL_UNSIGNED_INT_8_8_8_8_REV,
+ kUnsignedInt10x10x10x2 = GL_UNSIGNED_INT_10_10_10_2,
+ kUnsignedInt2x10x10x10Rev = GL_UNSIGNED_INT_2_10_10_10_REV,
+ };
+
+ // Filtering techniques.
+ enum class MinFilter : GLenum {
+ kNearest = GL_NEAREST,
+ kLinear = GL_LINEAR,
+ kNearestMipmapNearest = GL_NEAREST_MIPMAP_NEAREST,
+ kLinearMipmapNearest = GL_LINEAR_MIPMAP_NEAREST,
+ kNearestMipmapLinear = GL_NEAREST_MIPMAP_LINEAR,
+ kLinearMipmapLinear = GL_LINEAR_MIPMAP_LINEAR,
+ };
+ enum class MagFilter : GLenum {
+ kNearest = GL_NEAREST,
+ kLinear = GL_LINEAR,
+ };
+
+ // Wrapping techniques.
+ enum class Wrap : GLenum {
+ kClampToEdge = GL_CLAMP_TO_EDGE,
+ kClampToBorder = GL_CLAMP_TO_BORDER,
+ kMirroredRepeat = GL_MIRRORED_REPEAT,
+ kRepeat = GL_REPEAT,
+ kMirrorClampToEdge = GL_MIRROR_CLAMP_TO_EDGE,
+ };
+
+ Texture(Texture&&) noexcept = default;
+ Texture& operator=(Texture&&) noexcept = default;
+
+ virtual ~Texture() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glDeleteTextures(1, &texture_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ // The GL identifier for this texture.
+ unsigned int id() const noexcept { return texture_; }
+
+ protected:
+ int levels_;
+ GLuint texture_;
+
+ private:
+ friend class Texture2d;
+
+ explicit Texture(int levels) noexcept(!gl_internal::kThreadSafetyChecks)
+ : levels_(levels) {
+ gl_internal::CheckThreadSafety();
+ }
+};
+
+// A two-dimensional texture.
+class Texture2d final : public Texture {
+ public:
+ enum class Dimension : GLenum {
+ kS = GL_TEXTURE_WRAP_S,
+ kT = GL_TEXTURE_WRAP_T,
+ };
+
+ // Creates a two-dimensional texture with the specified pixel format, width,
+ // and height. The number of levels is equal to the number of mipmap layers
+ // for this texture; if you're not using mipmaps, you can leave it as 1.
+ explicit Texture2d(Format format, int width, int height, int levels = 1)
+ : Texture(levels), width_(width), height_(height) {
+ glCreateTextures(GL_TEXTURE_2D, 1, &texture_);
+ gl_internal::UnnecessaryErrorCheck();
+
+ glTextureStorage2D(texture_, levels, FromEnum(format), width_, height_);
+ // This error check is necessary because `format` could have been passed as
+ // an invalid GLenum.
+ gl_internal::ErrorCheck();
+ }
+
+ int width() const noexcept { return width_; }
+ int height() const noexcept { return height_; }
+
+ // Copies data from the specified buffer into GPU memory and associates it
+ // with this texture. width, height, format, and type describe the formatting
+ // of the data buffer, not this texture!
+ //
+ // There are no alignment restrictions on the buffer. It should consist of
+ // densely packed rows of pixels, left to right and top to bottom.
+ //
+ // As in the constructor, the optional level argument specifies which mipmap
+ // layer to populate. If you're not using mipmaps, you can leave it as 0.
+ void LoadSubimage(int width, int height, PixelFormat format, PixelType type,
+ const void* pixels, int level = 0);
+
+ // Fills all texture layers except layer 0 with automatically generated
+ // mipmaps. These mipmaps will generally be of lower quality than you would
+ // get if you generated them manually and loaded them with LoadSubimage.
+ // However, using them will yield a higher quality render than not using
+ // mipmaps at all.
+ void GenerateMipmaps() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glGenerateTextureMipmap(texture_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ void SetWrap(Dimension dimension, Wrap wrap) {
+ gl_internal::CheckThreadSafety();
+ glTexParameteri(GL_TEXTURE_2D, FromEnum(dimension), FromEnum(wrap));
+ // This error check is necessary because `dimension` or `wrap` could have
+ // been passed as an invalid GLenum.
+ gl_internal::ErrorCheck();
+ }
+
+ void SetMinFilter(MinFilter filter) {
+ gl_internal::CheckThreadSafety();
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, FromEnum(filter));
+ // This error check is necessary because `filter` could have been passed as
+ // an invalid GLenum.
+ gl_internal::ErrorCheck();
+ }
+
+ void SetMagFilter(MagFilter filter) {
+ gl_internal::CheckThreadSafety();
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, FromEnum(filter));
+ // This error check is necessary because `filter` could have been passed as
+ // an invalid GLenum.
+ gl_internal::ErrorCheck();
+ }
+
+ private:
+ int width_;
+ int height_;
+};
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_TEXTURE_H_
diff --git a/src/gl/vertex_array.cc b/src/gl/vertex_array.cc
new file mode 100644
index 0000000..067728e
--- /dev/null
+++ b/src/gl/vertex_array.cc
@@ -0,0 +1,73 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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 "src/gl/vertex_array.h"
+
+#include <stdint.h>
+
+#include "src/gl/error.h"
+#include "src/util.h"
+#include "third_party/abseil/absl/strings/str_cat.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+using ::gl_internal::CheckThreadSafety;
+using ::gl_internal::ErrorCheck;
+using ::gl_internal::UnnecessaryErrorCheck;
+
+void VertexArray::SetVertexAttributeFormat(int index, int size,
+ VertexAttributeType type, int offset,
+ bool normalized) {
+ CheckThreadSafety();
+
+ glEnableVertexArrayAttrib(array_, index);
+ // An error check is necessary here because the index might be too large for
+ // OpenGL to handle, but it can be folded into a future error check.
+ UnnecessaryErrorCheck();
+
+ switch (type) {
+ case VertexAttributeType::kHalfFloat:
+ case VertexAttributeType::kFloat:
+ glVertexArrayAttribFormat(array_, index, size, FromEnum(type), normalized,
+ offset);
+ break;
+ case VertexAttributeType::kByte:
+ case VertexAttributeType::kShort:
+ case VertexAttributeType::kInt:
+ case VertexAttributeType::kFixed:
+ case VertexAttributeType::kUnsignedByte:
+ case VertexAttributeType::kUnsignedShort:
+ case VertexAttributeType::kUnsignedInt:
+ case VertexAttributeType::kInt2x10x10x10Rev:
+ case VertexAttributeType::kUnsignedInt2x10x10x10Rev:
+ case VertexAttributeType::kUnsignedInt10f11f11fRev:
+ glVertexArrayAttribIFormat(array_, index, size, FromEnum(type), offset);
+ break;
+ case VertexAttributeType::kDouble:
+ glVertexArrayAttribLFormat(array_, index, size, FromEnum(type), offset);
+ break;
+ default:
+ throw std::invalid_argument(
+ absl::StrCat("GL: invalid VertexAttributeType ", type));
+ }
+ // This error check is necessary because a wide variety of argument
+ // combinations are invalid.
+ ErrorCheck();
+
+ glVertexArrayAttribBinding(array_, index, /*bindingindex=*/0);
+ UnnecessaryErrorCheck();
+}
+
+} // namespace gl
diff --git a/src/gl/vertex_array.h b/src/gl/vertex_array.h
new file mode 100644
index 0000000..5799c60
--- /dev/null
+++ b/src/gl/vertex_array.h
@@ -0,0 +1,108 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// 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
+//
+// https://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.
+
+// Vertex array objects.
+
+#ifndef GLPLANET_SRC_GL_VERTEX_ARRAY_H_
+#define GLPLANET_SRC_GL_VERTEX_ARRAY_H_
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "src/gl/buffer.h"
+#include "src/gl/error.h"
+#include "third_party/abseil/absl/types/optional.h"
+#include "third_party/glew/include/GL/glew.h"
+
+namespace gl {
+
+enum class VertexAttributeType : GLenum {
+ kByte = GL_BYTE,
+ kShort = GL_SHORT,
+ kInt = GL_INT,
+ kFixed = GL_FIXED,
+ kFloat = GL_FLOAT,
+ kHalfFloat = GL_HALF_FLOAT,
+ kDouble = GL_DOUBLE,
+ kUnsignedByte = GL_UNSIGNED_BYTE,
+ kUnsignedShort = GL_UNSIGNED_SHORT,
+ kUnsignedInt = GL_UNSIGNED_INT,
+ kInt2x10x10x10Rev = GL_INT_2_10_10_10_REV,
+ kUnsignedInt2x10x10x10Rev = GL_UNSIGNED_INT_2_10_10_10_REV,
+ kUnsignedInt10f11f11fRev = GL_UNSIGNED_INT_10F_11F_11F_REV,
+};
+
+// A VAO.
+class VertexArray final {
+ public:
+ explicit VertexArray() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glCreateVertexArrays(1, &array_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ VertexArray(VertexArray&&) noexcept = default;
+ VertexArray& operator=(VertexArray&&) noexcept = default;
+
+ ~VertexArray() noexcept(!(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glDeleteVertexArrays(1, &array_);
+ gl_internal::UnnecessaryErrorCheck();
+ }
+
+ // Attaches the specified vertex buffer to the VAO.
+ void SetVertexBuffer(VertexBuffer vertices) {
+ gl_internal::CheckThreadSafety();
+ glVertexArrayVertexBuffer(array_, /*bindingindex=*/0, vertices.id(),
+ /*offset=*/0, vertices.element_size());
+ // This error check is necessary because the elements of the VBO might be
+ // too large for the implementation to handle.
+ gl_internal::ErrorCheck();
+ vertices_ = std::move(vertices);
+ }
+
+ // Specifies the organization of data in the attached VAO.
+ void SetVertexAttributeFormat(int index, int size, VertexAttributeType type,
+ int offset, bool normalized = false);
+
+ // Attaches the specified element buffer to the VAO. The element buffer must
+ // outlive this VAO.
+ void SetElementBuffer(ElementBuffer elements) noexcept(
+ !(gl_internal::kThreadSafetyChecks ||
+ gl_internal::kAggressiveErrorChecking)) {
+ gl_internal::CheckThreadSafety();
+ glVertexArrayElementBuffer(array_, elements.id());
+ gl_internal::UnnecessaryErrorCheck();
+ elements_ = std::move(elements);
+ }
+
+ // Returns the element buffer attached to this VAO.
+ const ElementBuffer& element_buffer() const noexcept { return elements_; }
+
+ // The GL identifier for this VAO.
+ unsigned int id() const noexcept { return array_; }
+
+ private:
+ GLuint array_;
+
+ VertexBuffer vertices_;
+ ElementBuffer elements_;
+};
+
+} // namespace gl
+
+#endif // GLPLANET_SRC_GL_VERTEX_ARRAY_H_