13 Commits
22 changed files with 870 additions and 41 deletions
+4 -1
View File
@@ -7,7 +7,10 @@
namespace nb {
uint64_t GetPID();
uint64_t getTID();
uint64_t GetTID();
extern thread_local const uint64_t CurrentTID;
static const uint64_t MainTID = GetTID();
} // namespace nb
+2
View File
@@ -26,4 +26,6 @@ uint64_t GetTID() {
#ifdef _NB_TARGET_LINUX
#endif // _NB_TARGET_LINUX
thread_local const uint64_t CurrentTID = GetTID();
} // namespace nb
+70
View File
@@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.10)
project(NBData VERSION 0.1)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
toAbsolutePath(NB_DATA_SOURCE
./src/FFT.cpp
)
toAbsolutePath(NB_DATA_INCLUDE
./include/NBData/FFT.hpp
)
set(NB_DATA_SOURCE ${NB_DATA_SOURCE} PARENT_SCOPE)
set(NB_DATA_INCLUDE ${NB_DATA_INCLUDE} PARENT_SCOPE)
add_library(NBData ${NB_DATA_SOURCE})
add_library(NBEngine::Data ALIAS NBData)
target_link_libraries(NBData
NBCore
)
target_include_directories(NBData
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
PUBLIC "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
export(
TARGETS NBData
FILE "${CMAKE_BINARY_DIR}/cmake/NBDataTargets.cmake"
NAMESPACE NBEngine::
)
configure_package_config_file(
"NBDataConfig.cmake.in"
"${CMAKE_BINARY_DIR}/cmake/NBDataConfig.cmake"
INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}/cmake"
PATH_VARS CMAKE_INSTALL_LIBDIR
)
write_basic_package_version_file(
"${CMAKE_BINARY_DIR}/cmake/NBDataConfigVersion.cmake"
COMPATIBILITY AnyNewerVersion
)
if (NBENGINE_INSTALL)
message("Installing NBData to ${CMAKE_INSTALL_PREFIX}")
install(
TARGETS NBData
EXPORT NBDataTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
install(
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/NBData"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
)
install(
EXPORT NBDataTargets
DESTINATION "${CMAKE_INSTALL_PREFIX}/cmake"
NAMESPACE NBEngine::
)
install(FILES
"${CMAKE_BINARY_DIR}/cmake/NBDataConfig.cmake"
"${CMAKE_BINARY_DIR}/cmake/NBDataConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_PREFIX}/CMake"
)
endif()
if (NB_BUILD_TESTS)
add_subdirectory(./tests )
endif()
+2
View File
@@ -0,0 +1,2 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/NBDataTargets.cmake")
+252
View File
@@ -0,0 +1,252 @@
#pragma once
#ifndef _NB_DATA_FFT
#define _NB_DATA_FFT
#include <cmath>
#include <complex>
#include <unordered_map>
#include <vector>
#include <NBCore/Errors.hpp>
#ifndef PI_VALUE
#define PI_VALUE 3.1415926535
#endif
/*! @file FFT.hpp */
namespace nb {
using namespace std::complex_literals;
constexpr double operator""_pi(long double val) {
return val*PI_VALUE;
}
constexpr double operator""_pi(unsigned long long val) {
return val*PI_VALUE;
}
constexpr float operator""_pif(long double val) {
return val*PI_VALUE;
}
constexpr float operator""_pif(unsigned long long val) {
return val*PI_VALUE;
}
template<typename T>
bool isPowerOf2(const T& x) {
bool ret = false;
unsigned int mask=1;
for (int i=0; i<(8*sizeof(std::declval<T>())); ++i) {ret ^= x&mask; mask<<=1;}
return ret;
}
/*!
@brief Calculates the complex-valued twiddle factor given the `phase`
@param phase A double representing the phase, or `(k*n)/N`, of a DFT,
in units of 2*Pi.
@return A std::complex<T> representing the complex twiddle factor of
the DFT at that `phase`, calculated via `exp(-2*Pi*i*phase)`.
Calculates the complex-valued twiddle factor given the `phase` in units
of the ratio around the circle (i.e. in units of 2*Pi), calculated via
`exp(-2*Pi*i*phase)`. Note the minus sign, as this function is for the
*forward* DFT.
*/
template<typename T>
std::complex<T> calculateTwiddleFactor(double phase) {
int sign = -1;
while(phase>0.5) { phase-=0.5; sign*=-1; }
return static_cast<std::complex<T>>(std::exp(2_pi*(sign)*(1i)*phase));
}
/*!
Stores the twiddle factors for Radix-2 DFT matrices for a given data type
(i.e. stores std::complex<T> values), which can be retrieved using `TwiddleFactor<T>::get`.
*/
template<typename T>
class TwiddleFactors2 {
protected:
static std::unordered_map<unsigned int, std::complex<T>> factors;
public:
/*!
@brief Retrieves the twiddle factor for a Radix-2 DFT
@param numerator The numerator (i.e. `k*n` of a DFT matrix) part of the phase
when defined in units of 2*Pi.
@param log2N The `log2(denominator)` (i.e. `/N` of a DFT matrix) part of the phase
when defined in units of 2*Pi.
@return A std::complex<T> for the value of the `numerator / (2^log2N)` twiddle factor
*/
static std::complex<T> get(unsigned int numerator, unsigned int log2N);
};
template<typename T>
std::complex<T>* fft2_impl(
const std::complex<T>* source,
std::complex<T>* dest,
unsigned int log2N,
unsigned int stride=1
) {
constexpr std::complex<T> J(0, 1);
switch(log2N) {
case 0:
dest[0] = source[0];
break;
case 1:
dest[0] = source[0] + source[stride];
dest[1] = source[0] - source[stride];
break;
case 2:
dest[0] = source[0] + source[stride] + source[2*stride] + source[3*stride];
dest[1] = source[0] - J*source[stride] - source[2*stride] + J*source[3*stride];
dest[2] = source[0] - source[stride] + source[2*stride] - source[3*stride];
dest[3] = source[0] + J*source[stride] - source[2*stride] - J*source[3*stride];
break;
default:
const unsigned int halfway = 1<<(log2N-1);
fft2_impl(source, dest, log2N-1, 2*stride);
fft2_impl(&(source[stride]), &(dest[halfway]), log2N-1, 2*stride);
std::complex<T> twiddle;
std::complex<T> p, q;
for (unsigned int i=0; i < halfway; ++i) {
twiddle = TwiddleFactors2<T>::get(i, log2N);
p = dest[i];
q = dest[halfway+i]*twiddle;
dest[i] = p+q;
dest[halfway+i] = p-q;
}
break;
}
return dest;
}
template<typename T>
std::complex<T>* dft_impl(
const std::complex<T>* source,
std::complex<T>* dest,
unsigned int N
) {
for (int k=0; k < N; ++k) {
dest[k] = 0;
for (int n = 0; n < N; ++n) {
dest[k] += source[n] * calculateTwiddleFactor<T>(double(n*k)/double(N));
}
}
return dest;
}
/*!
@brief Computes the minimal Radix-2 DFT on a std::vector<std::complex<T>>
@param values A std::vector<std::complex<T>> representing the time domain values
@return A std::vector<std::complex<T>>, representing the minimal Radix-2 DFT of `values`
Computes the Radix-2 DFT on a std::vector<std::complex<T>>. If the input vector
`values` is not a power of 2, then the number of points in the DFT (and thus the
size of the returned frequency domain vector) is determined by
`2^ceil(log2(values.size()))`
*/
template<typename T>
std::vector<std::complex<T>> fft2(const std::vector<std::complex<T>>& values) {
const auto N = values.size();
unsigned int log2N=0;
while(1<<log2N < N) {log2N++;}
std::vector<std::complex<T>> temp(1<<log2N, 0);
std::vector<std::complex<T>> ret = temp;
for (int i=0; i < N; ++i) { temp[i] = values[i]; }
std::complex<T>* fft_results = fft2_impl(temp.data(), ret.data(), log2N);
return ret;
}
/*!
@brief Computes the minimal Radix-2 DFT on a `std::vector<T>`
@param values A std::vector<T> representing the time domain values with packed real and
imaginary parts
@return A `std::vector<T>`, representing the minimal Radix-2 DFT of `values` with packed
real and imaginary parts
@throw std::string Thrown when the size of the vector is not divisible by 2 (i.e.
there are unmatched real/imaginary pairs).
Computes the Radix-2 DFT on a std::vector<std::complex<T>>. Equivalent to
`fft2(const std::vector<std::complex<T>>)` if `values[2*i]` and `values[2*i+1]`
were mapped to the real and imaginary parts of the complex vector.
*/
template<typename T>
std::vector<T> fft2(const std::vector<T>& values) {
if (values.size()&1) {
THROW(Error<>(Error<>::VALUE_ERROR,
"Vector must be of even length to represent complex values."
));
}
std::complex<T> complex_input(values.data(), values.size()/2);
std::complex<T> complex_ret = fft2(complex_input);
return std::vector<T>(complex_ret.data(), complex_ret.size()*2);
}
/*!
@brief Computes the DFT on a std::vector<std::complex<T>>
@param values A std::vector<std::complex<T>> representing the time domain values
@return A std::vector<std::complex<T>> of the DFT of `values` in the frequency domain
Computes the DFT of the std::vector<std::complex<T>> `values`. Does so using the
naive O(N^2) matrix-multiplication method.
*/
template<typename T>
std::vector<std::complex<T>> dft(const std::vector<std::complex<T>>& x) {
std::vector<std::complex<T>> ret = x;
std::complex<T>* fft_results = dft_impl(x.data(), ret.data(), x.size());
return ret;
}
/*!
@brief Computes the DFT on a `std::vector<T>`
@param values A `std::vector<T>` representing the time domain values with packed real and
imaginary parts
@return A `std::vector<T>` of the DFT of `values` in the frequency domain with
packed real and imaginary parts
@throw std::string Thrown when the size of the vector is not divisible by 2 (i.e.
there are unmatched real/imaginary pairs).
Computes the DFT on a `std::vector<T>`. Equivalent to
`dft(const std::vector<std::complex<T>>)` if `values[2*i]` and `values[2*i+1]`
were mapped to the real and imaginary parts of the complex vector.
*/
template<typename T>
std::vector<T> dft(const std::vector<T>& values) {
if (values.size()&1) {
THROW(Error<>(Error<>::VALUE_ERROR,
"Vector must be of even length to represent complex values."
));
}
std::complex<T> complex_input(values.data(), values.size()/2);
std::complex<T> complex_ret = dft(complex_input);
return std::vector<T>(complex_ret.data(), complex_ret.size()*2);
}
template<typename T>
std::unordered_map<unsigned int, std::complex<T>> TwiddleFactors2<T>::factors = {};
template<typename T>
std::complex<T> TwiddleFactors2<T>::get(unsigned int a, unsigned int log2N) {
while(a && !(a&1)) { a>>=1; log2N--; }
const unsigned int N = 1<<log2N;
const double phase = double(a)/double(N);
const unsigned int key = a*N;
try {
return factors.at(key);
} catch (const std::out_of_range& e) {
if (a > N>>1 ) {
return (factors[key] = std::conj(TwiddleFactors2<T>::get(N-a, log2N)));
} else {
return (factors[key] = calculateTwiddleFactor<T>(phase));
}
}
}
} // namespace n
#endif // _NB_DATA_FFT
+6
View File
@@ -0,0 +1,6 @@
#include <NBCore/Errors.hpp>
#include <NBData/FFT.hpp>
namespace nb {
} // namespace nb
+22
View File
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.26.0)
if (NB_BUILD_TESTS)
enable_testing()
include(GoogleTest)
add_executable(TestData
./testFFT.cpp
)
target_link_libraries(TestData
NBData
GTest::gtest_main
)
gtest_discover_tests(TestData)
add_executable(FFTSpeedTest
./fftspeedtest.cpp
)
target_link_libraries(FFTSpeedTest
NBData
)
endif()
+5
View File
@@ -0,0 +1,5 @@
#include <NBData/FFT.hpp>
int main() {
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
#include <gtest/gtest.h>
#include <NBData/ComplexNumbers.hpp>
template<typename A, typename B>
void COMPLEX_EQ(const nb::Complex<A>& a, const nb::Complex<B>& b) {
ASSERT_EQ(a.r, b.r);
ASSERT_EQ(a.i, b.i);
}
template<typename A, typename B>
void COMPLEX_ALMOST_EQ(const nb::Complex<A>& a, const nb::Complex<B>& b) {
ASSERT_FLOAT_EQ(a.r, b.r);
ASSERT_FLOAT_EQ(a.i, b.i);
}
TEST(ComplexNumberTests, TestConstruction) {
COMPLEX_ALMOST_EQ(nb::Complex(), nb::Complex(0,0));
COMPLEX_ALMOST_EQ(nb::Complex(5.0), nb::Complex(5, 0));
COMPLEX_ALMOST_EQ(1.5_j, nb::Complex(0.0, 1.5));
}
TEST(ComplexNumberTests, TestAdd) {
auto x = nb::Complex(1.0) - nb::Complex(2.0, 1.0);
ASSERT_FLOAT_EQ(x.r, -1);
ASSERT_FLOAT_EQ(x.i, -1);
}
+68
View File
@@ -0,0 +1,68 @@
#include <gtest/gtest.h>
#include <NBData/FFT.hpp>
using namespace nb;
template<typename A, typename B>
void COMPLEX_EQ(const std::complex<A>& a, const std::complex<B>& b) {
ASSERT_EQ(a.real(), b.real());
ASSERT_EQ(a.imag(), b.imag());
}
template<typename A, typename B>
void COMPLEX_FLOAT_EQ(
const std::complex<A>& a,
const std::complex<B>& b,
const long double epsilon=0
) {
if (epsilon) {
ASSERT_NEAR(a.real(), b.real(), epsilon);
ASSERT_NEAR(a.imag(), b.imag(), epsilon);
} else {
ASSERT_FLOAT_EQ(a.real(), b.real());
ASSERT_FLOAT_EQ(a.imag(), b.imag());
}
}
template<typename A, typename B>
void COMPLEX_EQ_VEC(
const std::vector<std::complex<A>>& a,
const std::vector<std::complex<A>>& b
) {
if (a.size() != b.size()) { FAIL(); }
for (int i = 0; i < a.size(); ++i) {
COMPLEX_EQ(a[i], b[i]);
}
}
template<typename A, typename B>
void COMPLEX_FLOAT_EQ_VEC(
const std::vector<std::complex<A>>& a,
const std::vector<std::complex<B>>& b,
const long double epsilon
) {
if (a.size() != b.size()) { FAIL(); }
for (int i = 0; i < a.size(); ++i) {
COMPLEX_FLOAT_EQ(a[i], b[i], epsilon);
}
}
TEST(FFTTest, TestFFT) {
std::vector<std::vector<std::complex<double>>> input = {
{1.0, 0.5, 0},
{1,1,1,1,1,1,1,1},
};
std::vector<std::vector<std::complex<double>>> expected = {
{1.5,1.f-0.5if,0.5,1.f+0.5if},
{8, 0, 0, 0, 0, 0, 0, 0}
};
for (int i = 0; i < input.size(); ++i) {
COMPLEX_FLOAT_EQ_VEC(
nb::fft2(input[i]),
expected[i],
1e-10
);
}
}
@@ -74,12 +74,12 @@ class Buffer : public OpenGLObject {
if (!_id) {
OPENGL_CALL(glGenBuffers(1, &_id));
}
bind();
this->bind();
return _id;
}
virtual void remove() override {
if (_id) {
unbind();
this->unbind();
OPENGL_CALL(glDeleteBuffers(1, &_id));
_id = 0;
}
@@ -160,6 +160,18 @@ class ArrayBuffer : public Buffer {
}
};
class UniformBuffer : public Buffer {
protected:
GLuint _index;
public:
UniformBuffer();
void bind() const override;
GLuint bufferIndex() const;
GLuint bufferIndex(GLuint);
};
class ElementBuffer : protected Buffer {
protected:
GLenum _glslType;
+16 -7
View File
@@ -158,6 +158,8 @@ class Image<Pixel<T, Channels...>> {
using Codes = ImageError::Codes;
bool _data_managed;
T* _data;
Image(unsigned int x, unsigned int y, T* data, bool managed)
: width(x), height(y), _data(data), _data_managed(managed) {}
void clear() {
if (_data_managed) {
delete[] _data;
@@ -171,19 +173,21 @@ class Image<Pixel<T, Channels...>> {
static constexpr size_t NumberChannels = sizeof...(Channels);
const unsigned int width;
const unsigned int height;
static Image&& CreateImageStorage(
static Image CreateImageStorage(
unsigned int width,
unsigned int height,
T* data=nullptr
) {
unsigned int data_size = NumberChannels*width*height*sizeof(T);
T* data_ptr = new T[data_size];
auto& ret = Image(width, height, data_ptr);
Image ret(width, height, data_ptr, true);
if (data) {
std::memcpy(data_ptr, data, data_size);
} else {
std::memset(data_ptr, 0x00, data_size);
}
ret._data_managed = true;
return std::move(ret);
return ret;
}
Image(unsigned int x, unsigned int y, T* data=nullptr)
: width(x), height(y), _data(data), _data_managed(false) {}
@@ -201,8 +205,13 @@ class Image<Pixel<T, Channels...>> {
}
const Error<> sizeErr = sizeCompare(cpy);
if (sizeErr.code!=1) { THROW(sizeErr); }
_data = cpy._data;
return this;
if (!cpy._data_managed) {
_data = cpy._data;
_data_managed = false;
} else {
*this = cpy.copy();
}
return *this;
}
Image& operator=(Image&& mv) {
if (_data_managed) {
@@ -213,7 +222,7 @@ class Image<Pixel<T, Channels...>> {
_data = mv._data;
_data_managed = mv._data_managed;
mv._data_managed = false;
return this;
return *this;
}
virtual ~Image() {
@@ -221,7 +230,7 @@ class Image<Pixel<T, Channels...>> {
}
const T* data() const { return _data; }
Image<PixelType>&& copy() const {
Image<PixelType> copy() const {
return CreateImageStorage(width, height, _data);
}
PixelReference<PixelType> at(unsigned int x, unsigned int y) {
@@ -32,6 +32,7 @@ class OpenGLError : public Error<OpenGLError> {
static const std::string type;
static const ErrorCodeMap ErrorMessages;
static OpenGLError status();
static OpenGLError status(const std::string& note);
};
class OpenGLObjectError : public Error<OpenGLObjectError> {
@@ -41,7 +42,7 @@ class OpenGLObjectError : public Error<OpenGLObjectError> {
using Base::Base;
enum Codes : unsigned int {
UNDEFINED, HANGING_OBJECT, INVALID_OBJECT
UNDEFINED, HANGING_OBJECT, INVALID_OBJECT, INVALID_CONTEXT
};
static const std::string type;
@@ -60,13 +61,16 @@ class OpenGLObject {
virtual void unbind() const = 0;
GLuint id() const { return _id; }
protected:
using Codes = OpenGLObjectError::Codes;
OpenGLObject() = default;
OpenGLObject();
void checkObjectContextValidity();
virtual GLuint declare() = 0;
virtual void remove() = 0;
const GLFWwindow* const _arena;
GLuint _id = 0;
};
@@ -80,21 +84,21 @@ class OpenGLObject {
unsigned int line,
Fn fn
) {
auto check_ = [file, line]() {
auto check_ = [file, line](const std::string& note="") {
const auto no_err = nb::OpenGLError::NO_ERROR;
auto stat = nb::OpenGLError::status();
auto stat = nb::OpenGLError::status(note);
if (stat.code != no_err) {
THROW_W_CODE_LOC(file, line, stat);
}
};
if constexpr(std::is_same_v<std::invoke_result_t<Fn>, void>) {
check_();
check_("Pre-call state");
fn();
check_();
check_("Post-call state");
} else {
check_();
check_("Pre-call state");
auto ret = fn();
check_();
check_("Post-call state");
return ret;
}
}
@@ -9,6 +9,7 @@
#include <NBCore/Errors.hpp>
#include <NBCore/Utils.hpp>
#include <NBGraphics/Buffers.hpp>
#include <NBGraphics/OGLObjects.hpp>
namespace nb {
@@ -112,17 +113,22 @@ class Program : public OpenGLObject {
Program& operator=(Program&&) = delete;
operator bool();
~Program() { remove(); }
virtual void bind() const override {
void bind() const override {
OPENGL_CALL(glUseProgram(_id));
}
virtual void unbind() const override { /* TODO: Some warning of some kind perhaps*/ }
void unbind() const override { /* TODO: Some warning of some kind perhaps*/ }
GLint status(GLenum) const;
std::string log() const;
void setUniform(const std::string& blockname, std::shared_ptr<UniformBuffer>);
void setUniformBinding(const std::string& blockname, GLuint index);
GLuint getUniformBinding(const std::string& blockname) const;
friend Base;
protected:
using Base::_id;
GLint _success;
std::unordered_map<std::string, GLuint> _uniformBindings;
virtual GLuint declare() override {
if (!_id) {
_id = OPENGL_CALL(glCreateProgram());
@@ -24,11 +24,11 @@ class RenderTarget : public OpenGLObject {
using Base = OpenGLObject;
using Base::_id;
RenderTarget(GLenum target) : Target(target) {}
RenderTarget(RenderTarget&& rhs)
: Target(rhs.Target), Base(std::move(rhs)) {}
public:
using Base::id;
RenderTarget(RenderTarget&& mv);
RenderTarget& operator=(RenderTarget&& mv);
const GLenum Target;
};
@@ -57,8 +57,10 @@ class Texture : public RenderTarget {
public:
using Base::id;
using Base::Target;
virtual void bind() const override {
Texture(Texture&& mv);
Texture& operator=(Texture&& mv);
void bind() const override {
if (_id) {
OPENGL_CALL(glBindTexture(Target, _id));
} else {
@@ -68,7 +70,7 @@ class Texture : public RenderTarget {
));
}
}
virtual void unbind() const override {
void unbind() const override {
OPENGL_CALL(glBindTexture(Target, 0));
}
@@ -119,13 +121,6 @@ struct OGLPixelFormat;
template<typename T, typename... Channels>
struct OGLPixelFormat<Pixel<T, Channels...>>;
template<>
struct OGLPixelFormat<Pixel<uint8_t, Red, Green, Blue>> {
static constexpr GLenum glBase = GL_RGB;
static constexpr GLenum glFormat = GL_RGB8UI;
static constexpr GLenum glData = GL_UNSIGNED_BYTE;
};
template<>
struct OGLPixelFormat<Pixel<uint8_t, Red, Green, Blue, Alpha>> {
static constexpr GLenum glBase = GL_RGBA;
@@ -133,6 +128,27 @@ struct OGLPixelFormat<Pixel<uint8_t, Red, Green, Blue, Alpha>> {
static constexpr GLenum glData = GL_UNSIGNED_BYTE;
};
template<>
struct OGLPixelFormat<Pixel<float, Red, Green, Blue, Alpha>> {
static constexpr GLenum glBase = GL_RGBA;
static constexpr GLenum glFormat = GL_RGBA32F;
static constexpr GLenum glData = GL_FLOAT;
};
template<>
struct OGLPixelFormat<Pixel<uint8_t, Red, Green, Blue>> {
static constexpr GLenum glBase = GL_RGB;
static constexpr GLenum glFormat = GL_RGB8UI;
static constexpr GLenum glData = GL_UNSIGNED_BYTE;
};
template<>
struct OGLPixelFormat<Pixel<float, Red, Green, Blue>> {
static constexpr GLenum glBase = GL_RGB;
static constexpr GLenum glFormat = GL_RGB32F;
static constexpr GLenum glData = GL_FLOAT;
};
template<>
struct OGLPixelFormat<Pixel<float, Red, Green>> {
static constexpr GLenum glBase = GL_RG;
@@ -140,6 +156,13 @@ struct OGLPixelFormat<Pixel<float, Red, Green>> {
static constexpr GLenum glData = GL_FLOAT;
};
template<>
struct OGLPixelFormat<Pixel<float, Red>> {
static constexpr GLenum glBase = GL_RED;
static constexpr GLenum glFormat = GL_R32F;
static constexpr GLenum glData = GL_FLOAT;
};
template<typename T>
class Texture2D;
@@ -226,5 +249,19 @@ class Texture1D<Pixel<T, Channels...>> : public Texture {
}
};
template <typename T>
class Texture1DArray;
template <typename... T>
class Texture1DArray<Pixel<T...>> : public Texture {
protected:
public:
using Texture::generateMipmaps;
Texture1DArray() : Texture(GL_TEXTURE_1D_ARRAY) {
}
};
} // namespace nb
#endif // _NB_TEXTURES
@@ -7,7 +7,9 @@
#include <array>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <NBCore/Errors.hpp>
#include <NBCore/Utils.hpp>
@@ -21,7 +23,8 @@ class GLFWError : public Error<GLFWError> {
using Base::Base;
enum Codes : unsigned int {
UNDEFINED, INIT_FAILED, GLFW_INTIALIZED, GLAD_FAILED
UNDEFINED, INIT_FAILED, GLFW_INTIALIZED, GLAD_FAILED,
NOT_MAIN, WINDOW_FAILED
};
static const std::string type;
@@ -84,5 +87,69 @@ protected:
int gladResponse = -1;
};
class OGLObjectArena;
class OGLWindow {
using GLFWHint = std::array<int, 2>;
private:
static inline void CheckForMainThread() {
if (nb::CurrentTID != nb::MainTID) {
THROW(GLFWError(GLFWError::Codes::NOT_MAIN));
}
}
protected:
static std::atomic<unsigned int> _count;
static std::atomic<bool> _glfwInitialized;
static thread_local std::shared_ptr<OGLWindow> _currWindow;
static thread_local GLFWwindow* _currArena;
std::string _title;
GLFWwindow* _window_ptr;
GLFWwindow* _arena;
static void SetHintInteger(const std::array<int, 2>&);
static void SetHintString(const std::tuple<int, std::string>&);
OGLWindow(
unsigned int,
unsigned int,
const std::string&,
const std::vector<std::array<int, 2>>&,
const std::vector<std::tuple<int, std::string>>&
);
public:
static inline bool IsGLFWInitialized() { return _glfwInitialized; }
static bool InitGLFW(const std::vector<GLFWHint>& hints={});
static bool TerminateGLFW();
static inline std::shared_ptr<OGLWindow> getCurrentWindow() { return _currWindow; }
static inline const GLFWwindow* getCurrentObjectArena() { return _currArena; }
static std::shared_ptr<OGLWindow> MakeCurrent(const std::shared_ptr<OGLWindow>&);
static inline std::shared_ptr<OGLWindow> DropCurrent() { return MakeCurrent(nullptr); }
static std::shared_ptr<OGLWindow> CreateWindow(
unsigned int x,
unsigned int y,
const std::string& title,
const std::vector<std::array<int, 2>>& integerHints={},
const std::vector<std::tuple<int, std::string>>& stringHints={}
);
OGLWindow(OGLWindow&&) = delete;
OGLWindow(const OGLWindow&) = delete;
~OGLWindow();
inline bool operator==(const OGLWindow& rhs) { return _window_ptr==rhs._window_ptr; }
int getWindowAttribute(int) const;
inline GLFWwindow* getWindowPtr() const { return _window_ptr; }
inline std::string getTitle() const { return _title; }
std::array<int, 2> getSize() const;
std::shared_ptr<OGLWindow> makeSharedContext();
void makeCurrent() const;
void setSize(unsigned int x, unsigned int y);
void setTitle(const std::string&);
};
};
#endif
+15
View File
@@ -74,6 +74,21 @@ void Buffer::subdata(const ByteVector& data_, GLintptr offset_) {
subdata(data_.data(), size_);
}
// class UniformBuffer
UniformBuffer::UniformBuffer() : Buffer(GL_UNIFORM_BUFFER) {}
void UniformBuffer::bind() const {
Buffer::bind();
OPENGL_CALL(glBindBufferBase(GL_UNIFORM_BUFFER, _index, id()));
}
GLuint UniformBuffer::bufferIndex() const { return _index; }
GLuint UniformBuffer::bufferIndex(GLuint idx) {
return (_index = idx);
}
// class ElementBuffer
ElementBuffer::ElementBuffer() : Buffer(GL_ELEMENT_ARRAY_BUFFER) {}
ByteVector ElementBuffer::data() const {
+21 -2
View File
@@ -1,4 +1,5 @@
#include <NBGraphics/OGLObjects.hpp>
#include <NBGraphics/Window.hpp>
#ifndef GLENUMSTRPAIR
#define GLENUMSTRPAIR(x) {x, #x}
@@ -10,7 +11,8 @@ const std::string OpenGLObjectError::type = "nb::OpenGLObjectError";
const ErrorCodeMap OpenGLObjectError::ErrorMessages = {
{ OpenGLObjectError::Codes::UNDEFINED, "Error" },
{OpenGLObjectError::Codes::HANGING_OBJECT, "Attempting to leave a hanging OpenGL object"},
{OpenGLObjectError::Codes::INVALID_OBJECT, "Attempting operation with invalid object"}
{OpenGLObjectError::Codes::INVALID_OBJECT, "Attempting operation with invalid object"},
{OpenGLObjectError::Codes::INVALID_CONTEXT, "Object not valid with current OpengGL context"}
};
const std::string OpenGLError::type = "nb::OpenGLError";
@@ -30,11 +32,28 @@ OpenGLError OpenGLError::status() {
return OpenGLError(glGetError());
}
OpenGLObject::OpenGLObject(OpenGLObject&& rval) {
OpenGLError OpenGLError::status(const std::string& note) {
return OpenGLError(glGetError(), note);
}
// class OpenGLObject
void OpenGLObject::checkObjectContextValidity() {
if (_arena != OGLWindow::getCurrentObjectArena()) {
THROW(OpenGLObjectError(OpenGLObjectError::INVALID_CONTEXT));
}
}
OpenGLObject::OpenGLObject(OpenGLObject&& rval) : _arena(rval._arena) {
checkObjectContextValidity();
*this = std::move(rval);
}
OpenGLObject::OpenGLObject() : _arena(OGLWindow::getCurrentObjectArena()) {}
OpenGLObject& OpenGLObject::operator=(OpenGLObject&& rhs) {
rhs.checkObjectContextValidity();
if (_id) { THROW(OpenGLObjectError(Codes::HANGING_OBJECT)); }
_id = rhs._id;
rhs._id = 0;
+24
View File
@@ -15,6 +15,8 @@ const ErrorCodeMap ProgramError::ErrorMessages = {
{ProgramErrCodes::LINKING_ERROR, "Linker error"}
};
// class Shader
Shader::Shader(GLenum target_, const std::string& source_)
: Shader(target_, std::vector<std::string>({source_})) {}
@@ -54,6 +56,8 @@ std::string Shader::log() const {
return ret;
}
// class Program
Program::Program(SharedVector<Shader> shaders_) {
declare();
for (auto shad_ptr : shaders_) {
@@ -83,6 +87,26 @@ std::string Program::log() const {
return ret;
}
void Program::setUniform(const std::string& blockname, std::shared_ptr<UniformBuffer> buffer) {
declare();
auto idx = OPENGL_CALL(glGetUniformBlockIndex(id(), blockname.data()));
if (idx == GL_INVALID_INDEX) {
THROW("something");
}
this->setUniformBinding(blockname, buffer->bufferIndex());
OPENGL_CALL(glUniformBlockBinding(
id(),
idx,
_uniformBindings[blockname]
));
}
void Program::setUniformBinding(const std::string& blockname, GLuint index) {
_uniformBindings[blockname] = index;
}
GLuint Program::getUniformBinding(const std::string& blockname) const {
return _uniformBindings.at(blockname);
}
}
+27
View File
@@ -2,6 +2,33 @@
namespace nb {
// class RenderTarget
RenderTarget::RenderTarget(RenderTarget&& mv) : Target(mv.Target) {
*this = std::move(mv);
}
RenderTarget& RenderTarget::operator=(RenderTarget&& mv) {
if (mv.Target != Target) {
THROW(OpenGLObjectError(
OpenGLObjectError::INVALID_OBJECT,
"Cannot assign RenderTarget of different types."
));
}
Base::operator=(std::move(mv));
return *this;
}
// class Texture
Texture::Texture(GLenum target_) : Base(target_) {}
Texture::Texture(Texture&& mv) : Base(mv.Target) {
*this = std::move(mv);
}
Texture& Texture::operator=(Texture&& mv) {
Base::operator=(std::move(mv));
return *this;
}
} // namespace nb
+150 -1
View File
@@ -22,7 +22,9 @@ const ErrorCodeMap GLFWError::ErrorMessages = {
{GLFWCodes::UNDEFINED, "Error"},
{GLFWCodes::INIT_FAILED, "GLFW initialization failed"},
{GLFWCodes::GLFW_INTIALIZED, "GLFW has already been initialized"},
{GLFWCodes::GLAD_FAILED, "GLAD initialization failed"}
{GLFWCodes::GLAD_FAILED, "GLAD initialization failed"},
{GLFWCodes::NOT_MAIN, "Action can only be done on main thread"},
{GLFWCodes::WINDOW_FAILED, "Failed to initialize window"}
};
using WindowErrorCodes = WindowError::Codes;
@@ -34,6 +36,9 @@ const ErrorCodeMap WindowError::ErrorMessages = {
{WindowErrorCodes::INIT_FAILED, "Could not intialized window"}
};
// class Window
int Window::getGLFWHint(int hint_key) {
if (GLFWHints.find(hint_key) == GLFWHints.end()) {
return 0;
@@ -183,4 +188,148 @@ void Window::resize(const std::array<uint16_t, 2> newSize) {
void Window::resize(const uint16_t x, const uint16_t y) { resize({x, y}); }
// class OGLWindow
std::atomic<bool> OGLWindow::_glfwInitialized = false;
thread_local std::shared_ptr<OGLWindow> OGLWindow::_currWindow = nullptr;
thread_local GLFWwindow* OGLWindow::_currArena = nullptr;
std::atomic<unsigned int> OGLWindow::_count = 0;
bool OGLWindow::InitGLFW(const std::vector<GLFWHint>& hints) {
if (nb::MainTID != GetTID()) {
THROW(GLFWError(GLFWError::NOT_MAIN));
}
if (!IsGLFWInitialized()) {
for (const auto& hint : hints) {
glfwInitHint(hint[0], hint[1]);
}
if (glfwInit()) {
OGLWindow::_glfwInitialized.store(true, std::memory_order_release);
} else {
THROW(GLFWError(GLFWError::INIT_FAILED));
}
}
return IsGLFWInitialized();
}
bool OGLWindow::TerminateGLFW() {
if (nb::MainTID != GetTID()) {
THROW(GLFWError(GLFWError::NOT_MAIN));
}
if (IsGLFWInitialized()) {
glfwTerminate();
OGLWindow::_glfwInitialized.store(false, std::memory_order_release);
}
return !IsGLFWInitialized();
}
void OGLWindow::SetHintInteger(const std::array<int, 2>& hint) {
CheckForMainThread();
glfwWindowHint(hint[0], hint[1]);
}
void OGLWindow::SetHintString(const std::tuple<int, std::string>& hint) {
CheckForMainThread();
glfwWindowHintString(std::get<0>(hint), std::get<1>(hint).c_str());
}
std::shared_ptr<OGLWindow> OGLWindow::MakeCurrent(const std::shared_ptr<OGLWindow>& window) {
auto tmp = _currWindow;
_currWindow = window;
if (window) {
glfwMakeContextCurrent(_currWindow->_window_ptr);
_currArena = window->_arena;
} else {
glfwMakeContextCurrent(nullptr);
_currArena = nullptr;
}
return tmp;
}
OGLWindow::OGLWindow(
unsigned int x,
unsigned int y,
const std::string& title,
const std::vector<std::array<int, 2>>& integerHints,
const std::vector<std::tuple<int, std::string>>& stringHints
) : _title(title) {
CheckForMainThread();
InitGLFW();
for (const auto& hint : integerHints) {
SetHintInteger(hint);
}
for (const auto& hint : stringHints) {
SetHintString(hint);
}
_window_ptr = glfwCreateWindow(
x,
y,
_title.c_str(),
NULL,
NULL
);
if (_window_ptr == NULL) {
THROW(GLFWError(GLFWError::Codes::WINDOW_FAILED));
}
_arena = _window_ptr;
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
THROW(GLFWError(GLFWError::Codes::GLAD_FAILED));
}
OGLWindow::_count.fetch_add(1);
}
std::shared_ptr<OGLWindow> OGLWindow::CreateWindow(
unsigned int x,
unsigned int y,
const std::string& title,
const std::vector<std::array<int, 2>>& integerHints,
const std::vector<std::tuple<int, std::string>>& stringHints
) {
return std::shared_ptr<OGLWindow>( new OGLWindow(
x,
y,
title,
integerHints,
stringHints
) );
}
OGLWindow::~OGLWindow() {
CheckForMainThread();
if (_window_ptr) {
if (_window_ptr == _currWindow->_window_ptr) {
_currWindow = nullptr;
_currArena = nullptr;
}
glfwDestroyWindow(_window_ptr);
_count.fetch_add(-1);
if (!_count.load()) {
TerminateGLFW();
}
}
}
int OGLWindow::getWindowAttribute(int attr) const {
CheckForMainThread();
return glfwGetWindowAttrib(_window_ptr, attr);
}
std::array<int, 2> OGLWindow::getSize() const {
CheckForMainThread();
int x, y;
glfwGetWindowSize(_window_ptr, &x, &y);
return {x, y};
}
void OGLWindow::setSize(unsigned int x, unsigned int y) {
CheckForMainThread();
glfwSetWindowSize(_window_ptr, x, y);
}
void OGLWindow::setTitle(const std::string& name) {
CheckForMainThread();
_title = name;
glfwSetWindowTitle(_window_ptr, _title.c_str());
}
};
+8 -5
View File
@@ -12,10 +12,13 @@
int main() {
LOG("Howdy!");
nb::Window window(400, 400, "Hello!");
window.setWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
window.setWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
window.init();
auto window = nb::OGLWindow::CreateWindow(
400,
400,
"Howdy!",
{{GLFW_CONTEXT_VERSION_MAJOR, 3},
{GLFW_CONTEXT_VERSION_MINOR, 3}}
);
auto vert = std::make_shared<nb::Shader>(
GL_VERTEX_SHADER,
@@ -66,7 +69,7 @@ int main() {
}, indxs);
LOG(prog.log());
GLFWwindow* window_ptr = window.getWindow();
GLFWwindow* window_ptr = window->getWindowPtr();
while(!glfwWindowShouldClose(window_ptr)) {
OPENGL_CALL(glClearColor(0.2f, 0.3f, 0.3f, 1.0f));
OPENGL_CALL(glClear(GL_COLOR_BUFFER_BIT));