diff options
author | Benjamin Barenblat <bbarenblat@gmail.com> | 2022-01-17 23:12:32 -0500 |
---|---|---|
committer | Benjamin Barenblat <bbarenblat@gmail.com> | 2022-01-30 15:55:27 -0500 |
commit | d0e18bdb7924c71cdca8dd983711171d87ef28be (patch) | |
tree | 6be11aae0b7c8874e6507b75b4ef26d1353952c7 /src/gl |
glplanet draws Earth like it currently appears from space, putting
nighttime areas in shadow and daytime areas in light. It’s modeled
after Xplanet (http://xplanet.sourceforge.net/), but whereas Xplanet is
entirely a CPU-resident program, glplanet draws using OpenGL. It’s thus
much less resource-intensive, particularly when using high-resolution
textures.
Diffstat (limited to 'src/gl')
-rw-r--r-- | src/gl/README | 16 | ||||
-rw-r--r-- | src/gl/buffer.cc | 105 | ||||
-rw-r--r-- | src/gl/buffer.h | 206 | ||||
-rw-r--r-- | src/gl/draw.cc | 56 | ||||
-rw-r--r-- | src/gl/draw.h | 217 | ||||
-rw-r--r-- | src/gl/error.cc | 84 | ||||
-rw-r--r-- | src/gl/error.h | 68 | ||||
-rw-r--r-- | src/gl/init.cc | 95 | ||||
-rw-r--r-- | src/gl/init.h | 86 | ||||
-rw-r--r-- | src/gl/shader.cc | 204 | ||||
-rw-r--r-- | src/gl/shader.h | 211 | ||||
-rw-r--r-- | src/gl/texture.cc | 43 | ||||
-rw-r--r-- | src/gl/texture.h | 259 | ||||
-rw-r--r-- | src/gl/vertex_array.cc | 73 | ||||
-rw-r--r-- | src/gl/vertex_array.h | 108 |
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_ |