#include "Gal.hpp" #include #include #include #include #include "Utility.hpp" using namespace Gal; struct ImplOgl; struct ShaderOgl; struct FramebufferOgl; struct ShaderParametersBufferOgl; class OglState { GLuint activeFbo = 0; GLuint activeVao = 0; GLuint activeVbo = 0; GLuint activeEbo = 0; GLuint activeProgram = 0; GLuint activeTexture[16] = { 0 }; GLuint activeTextureUnit = 0; GLint vpX = 0, vpY = 0; GLsizei vpW = 0, vpH = 0; bool blending = false; public: void BindFbo(GLuint fbo) { if (fbo != activeFbo) { glBindFramebuffer(GL_FRAMEBUFFER, fbo); activeFbo = fbo; } glCheckError(); } void BindVao(GLuint vao) { if (vao != activeVao) { glBindVertexArray(vao); activeVao = vao; } glCheckError(); } void BindVbo(GLuint vbo) { if (vbo != activeVbo) { glBindBuffer(GL_ARRAY_BUFFER, vbo); activeVbo = vbo; } glCheckError(); } void BindEbo(GLuint ebo) { if (ebo != activeEbo) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); activeEbo = ebo; } glCheckError(); } void SetTextureUnit(size_t textureUnit) { if (textureUnit != activeTextureUnit) { glActiveTexture(GL_TEXTURE0 + textureUnit); activeTextureUnit = textureUnit; } glCheckError(); } void BindTexture(GLenum type, GLuint texture, size_t textureUnit = 17) { if (textureUnit >= 16) textureUnit = activeTextureUnit; if (activeTexture[textureUnit] != texture) { SetTextureUnit(textureUnit); glBindTexture(type, texture); activeTexture[textureUnit] = texture; } glCheckError(); } void UseProgram(GLuint program) { if (program != activeProgram) { glUseProgram(program); activeProgram = program; } glCheckError(); } void SetViewport(GLint x, GLint y, GLsizei w, GLsizei h) { if (x != vpX || y != vpY || w != vpW || h != vpH) { glViewport(x, y, w, h); vpX = x; vpY = y; vpW = w; vpH = h; } glCheckError(); } void EnableBlending(bool enable) { if (enable != blending) { blending = enable; if (blending) glEnable(GL_BLEND); else glDisable(GL_BLEND); } } void ReleaseFbo(GLuint vao) { if (activeVao == vao) activeVao = 0; } void ReleaseVao(GLuint fbo) { if (activeFbo == fbo) activeEbo = 0; } void ReleaseVbo(GLuint vbo) { if (activeVbo == vbo) activeVbo = 0; if (activeEbo == vbo) activeEbo = 0; } void ReleaseProgram(GLuint program) { if (activeProgram == program) activeProgram = 0; } void ReleaseTexture(GLuint texture) { for (auto& activeTex : activeTexture) { if (activeTex == texture) activeTex = 0; } } } oglState; enum class GlResourceType { Vbo, Vao, Texture, Fbo, Program, None, }; class GlResource { GlResourceType type = GlResourceType::None; GLuint res = 0; public: GlResource() = default; GlResource(GLuint resource, GlResourceType resType) noexcept : res(resource), type(resType) {} GlResource(const GlResource&) = delete; GlResource(GlResource&& rhs) noexcept { std::swap(this->res, rhs.res); std::swap(this->type, rhs.type); } GlResource& operator=(const GlResource&) = delete; GlResource& operator=(GlResource&& rhs) noexcept { std::swap(this->res, rhs.res); std::swap(this->type, rhs.type); return *this; } ~GlResource() { switch (type) { case GlResourceType::Vbo: oglState.ReleaseVbo(res); glDeleteBuffers(1, &res); break; case GlResourceType::Vao: oglState.ReleaseVao(res); glDeleteVertexArrays(1, &res); break; case GlResourceType::Texture: oglState.ReleaseTexture(res); glDeleteTextures(1, &res); break; case GlResourceType::Fbo: oglState.ReleaseFbo(res); glDeleteFramebuffers(1, &res); break; case GlResourceType::Program: oglState.ReleaseProgram(res); glDeleteProgram(res); break; case GlResourceType::None: default: break; } } operator GLuint() const noexcept { return res; } GLuint Get() const noexcept { return res; } }; std::unique_ptr impl; std::shared_ptr fbDefault; std::shared_ptr spbDefault; size_t GalTypeGetComponents(Gal::Type type) { switch (type) { case Type::Float: case Type::Double: case Type::Uint8: case Type::Uint16: case Type::Uint32: case Type::Int8: case Type::Int16: case Type::Int32: return 1; case Type::Vec2: case Type::Vec2u8: case Type::Vec2u16: case Type::Vec2u32: case Type::Vec2i8: case Type::Vec2i16: case Type::Vec2i32: return 2; case Type::Vec3: case Type::Vec3u8: case Type::Vec3u16: case Type::Vec3u32: case Type::Vec3i8: case Type::Vec3i16: case Type::Vec3i32: return 3; case Type::Vec4: case Type::Vec4u8: case Type::Vec4u16: case Type::Vec4u32: case Type::Vec4i8: case Type::Vec4i16: case Type::Vec4i32: case Type::Mat2: return 4; case Type::Mat3: return 9; case Type::Mat4: return 16; default: return 0; } return 0; } size_t GalTypeGetComponentSize(Gal::Type type) { switch (type) { case Type::Uint8: case Type::Int8: case Type::Vec2u8: case Type::Vec2i8: case Type::Vec3u8: case Type::Vec3i8: case Type::Vec4u8: case Type::Vec4i8: return 1; case Type::Uint16: case Type::Int16: case Type::Vec2u16: case Type::Vec2i16: case Type::Vec3u16: case Type::Vec3i16: case Type::Vec4u16: case Type::Vec4i16: return 2; case Type::Float: case Type::Uint32: case Type::Int32: case Type::Vec2: case Type::Vec2u32: case Type::Vec2i32: case Type::Vec3: case Type::Vec3u32: case Type::Vec3i32: case Type::Vec4: case Type::Vec4u32: case Type::Vec4i32: case Type::Mat2: case Type::Mat3: case Type::Mat4: return 4; case Type::Double: return 8; default: return 0; } } size_t GalTypeGetSize(Gal::Type type) { return GalTypeGetComponents(type) * GalTypeGetComponentSize(type); } GLenum GalTypeGetComponentGlType(Gal::Type type) { switch (type) { case Type::Float: case Type::Vec2: case Type::Vec3: case Type::Vec4: case Type::Mat2: case Type::Mat3: case Type::Mat4: return GL_FLOAT; case Type::Double: return GL_DOUBLE; case Type::Uint8: case Type::Vec2u8: case Type::Vec3u8: case Type::Vec4u8: return GL_UNSIGNED_BYTE; case Type::Uint16: case Type::Vec2u16: case Type::Vec3u16: case Type::Vec4u16: return GL_UNSIGNED_SHORT; case Type::Uint32: case Type::Vec2u32: case Type::Vec3u32: case Type::Vec4u32: return GL_UNSIGNED_INT; case Type::Int8: case Type::Vec2i8: case Type::Vec3i8: case Type::Vec4i8: return GL_BYTE; case Type::Int16: case Type::Vec2i16: case Type::Vec3i16: case Type::Vec4i16: return GL_SHORT; case Type::Int32: case Type::Vec2i32: case Type::Vec3i32: case Type::Vec4i32: return GL_INT; default: return 0; } return 0; } size_t GalFormatGetSize(Format format) { switch (format) { case Format::D24S8: return 4; case Format::R8: return 1; case Format::R8G8: return 2; case Format::R8G8B8: return 3; case Format::R8G8B8SN: return 3; case Format::R8G8B8A8: return 4; case Format::R32G32B32A32F: return 16; default: return 0; } return 0; } GLenum GalFormatGetGlLinearInternalFormat(Format format) { switch (format) { case Format::D24S8: return GL_DEPTH24_STENCIL8; case Format::R8: return GL_R8; case Format::R8G8: return GL_RG8; case Format::R8G8B8: return GL_RGB8; case Format::R8G8B8SN: return GL_RGB8_SNORM; case Format::R8G8B8A8: return GL_RGBA8; case Format::R32G32B32A32F: return GL_RGBA32F; default: return 0; } return 0; } GLenum GalFormatGetGlInternalFormat(Format format) { switch (format) { case Format::D24S8: return 0; case Format::R8: return 0; case Format::R8G8: return 0; case Format::R8G8B8: return GL_SRGB; case Format::R8G8B8SN: return 0; case Format::R8G8B8A8: return GL_SRGB_ALPHA; case Format::R32G32B32A32F: return 0; default: return 0; } return 0; } GLenum GalFormatGetGlFormat(Format format) { switch (format) { case Format::D24S8: return GL_DEPTH_STENCIL; case Format::R8: return GL_RED; case Format::R8G8: return GL_RG; case Format::R8G8B8: return GL_RGB; case Format::R8G8B8SN: return GL_RGB; case Format::R8G8B8A8: return GL_RGBA; case Format::R32G32B32A32F: return GL_RGBA; default: return 0; } return 0; } GLenum GalFormatGetGlType(Format format) { switch (format) { case Format::D24S8: return GL_UNSIGNED_INT_24_8; case Format::R8: return GL_UNSIGNED_BYTE; case Format::R8G8: return GL_UNSIGNED_BYTE; case Format::R8G8B8: return GL_UNSIGNED_BYTE; case Format::R8G8B8SN: return GL_BYTE; case Format::R8G8B8A8: return GL_UNSIGNED_BYTE; case Format::R32G32B32A32F: return GL_FLOAT; default: return 0; } return 0; } GLenum GalFilteringGetGlType(Filtering filtering) { switch (filtering) { case Filtering::Nearest: return GL_NEAREST; case Filtering::Bilinear: return GL_LINEAR; case Filtering::Trilinear: return GL_LINEAR_MIPMAP_LINEAR; case Filtering::Anisotropy: return GL_LINEAR; default: return 0; } return 0; } GLenum GalWrappingGetGlType(Wrapping wrapping) { switch (wrapping) { case Wrapping::Repeat: return GL_REPEAT; case Wrapping::Clamp: return GL_CLAMP_TO_EDGE; case Wrapping::Mirror: return GL_MIRRORED_REPEAT; default: return 0; } return 0; } GLenum glCheckError_(const char* file, int line) { OPTICK_EVENT(); GLenum errorCode; while ((errorCode = glGetError()) != GL_NO_ERROR) { std::string error; switch (errorCode) { case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; } LOG(ERROR) << "OpenGL error: " << error << " at " << file << ":" << line; } return errorCode; } #ifndef NDEBUG #define glCheckError() glCheckError_(__FILE__, __LINE__) #else #define glCheckError() #endif // !NDEBUG void GLAPIENTRY glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char* message, const void* userParam) { // ignore non-significant error/warning codes if (id == 131169 || id == 131185 || id == 131218 || id == 131204) return; el::Level level = el::Level::Error; std::string sourceText; std::string typeText; std::string severityText; switch (severity) { case GL_DEBUG_SEVERITY_HIGH: severityText = "HIGH"; break; case GL_DEBUG_SEVERITY_MEDIUM: severityText = "MEDIUM"; break; case GL_DEBUG_SEVERITY_LOW: severityText = "LOW"; level = el::Level::Warning; break; case GL_DEBUG_SEVERITY_NOTIFICATION: severityText = "NOTIFY"; level = el::Level::Info; break; } switch (source) { case GL_DEBUG_SOURCE_API: sourceText = "API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: sourceText = "Window System"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceText = "Shader Compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: sourceText = "Third Party"; break; case GL_DEBUG_SOURCE_APPLICATION: sourceText = "Application"; break; case GL_DEBUG_SOURCE_OTHER: sourceText = "Other"; break; default: sourceText = std::to_string(source); break; } switch (type) { case GL_DEBUG_TYPE_ERROR: typeText = "Error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeText = "Deprecated Behaviour"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeText = "Undefined Behaviour"; break; case GL_DEBUG_TYPE_PORTABILITY: typeText = "Portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: typeText = "Performance"; break; case GL_DEBUG_TYPE_MARKER: typeText = "Marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: typeText = "Push Group"; break; case GL_DEBUG_TYPE_POP_GROUP: typeText = "Pop Group"; break; case GL_DEBUG_TYPE_OTHER: typeText = "Other"; break; default: typeText = std::to_string(type); break; } std::string log = "OpenGL debug (" + std::to_string(id) + ") [" + severityText + "][" + sourceText + "][" + typeText + "]: \n" + message; switch (level) { case el::Level::Error: LOG(ERROR) << log; break; case el::Level::Warning: LOG(WARNING) << log; break; case el::Level::Info: LOG(INFO) << log; break; default: LOG(ERROR) << log; break; } } struct ShaderOgl : public Shader { bool isVertex = true; std::string code; }; struct BufferBindingOgl : public BufferBinding { BufferBindingOgl(size_t id) : bufferId(id) {} const size_t bufferId; static constexpr size_t indexValue = (std::numeric_limits::max)(); //parenthess for windows' max macro }; struct BufferOgl : public Buffer { GlResource vbo; virtual void SetData(std::vector&& data) override { oglState.BindVbo(vbo); glBufferData(GL_ARRAY_BUFFER, data.size(), data.data(), GL_STATIC_DRAW); oglState.BindVbo(0); glCheckError(); } }; struct TextureConfigOgl : public TextureConfig { Format format = Format::R8; size_t width = 1, height = 1, depth = 1; bool interpolateLayers = false; bool linear = true; GLenum type = 0; Filtering min = Filtering::Nearest, max = Filtering::Nearest; Wrapping wrap = Wrapping::Clamp; virtual void SetMinFilter(Filtering filter) override { min = filter; } virtual void SetMaxFilter(Filtering filter) override { max = filter; } virtual void SetWrapping(Wrapping wrapping) override { wrap = wrapping; } virtual void SetLinear(bool isLinear) override { linear = isLinear; } }; struct TextureOgl : public Texture { GLenum type; GlResource texture; Format format; size_t width, height, depth; bool linear; virtual std::tuple GetSize() override { return { width, height, depth }; } virtual void SetData(std::vector&& data, size_t mipLevel = 0) override { size_t expectedSize = width * height * depth * GalFormatGetSize(format); if (data.size() != expectedSize && !data.empty()) throw std::logic_error("Size of data is not valid for this texture"); GLenum internalFormat = linear ? GalFormatGetGlLinearInternalFormat(format) : GalFormatGetGlInternalFormat(format); oglState.BindTexture(type, texture); switch (type) { case GL_TEXTURE_1D: case GL_PROXY_TEXTURE_1D: break; case GL_TEXTURE_2D: case GL_PROXY_TEXTURE_2D: case GL_TEXTURE_1D_ARRAY: case GL_PROXY_TEXTURE_1D_ARRAY: case GL_TEXTURE_RECTANGLE: case GL_PROXY_TEXTURE_RECTANGLE: case GL_TEXTURE_CUBE_MAP_POSITIVE_X: case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: case GL_PROXY_TEXTURE_CUBE_MAP: glTexImage2D(type, mipLevel, internalFormat, width, height, 0, GalFormatGetGlFormat(format), GalFormatGetGlType(format), data.empty() ? nullptr : data.data()); break; case GL_TEXTURE_3D: case GL_PROXY_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: case GL_PROXY_TEXTURE_2D_ARRAY: glTexImage3D(type, mipLevel, internalFormat, width, height, depth, 0, GalFormatGetGlFormat(format), GalFormatGetGlType(format), data.empty() ? nullptr : data.data()); break; default: throw std::runtime_error("Unknown texture type"); } glCheckError(); oglState.BindTexture(type, 0); } virtual void SetSubData(size_t x, size_t y, size_t z, size_t w, size_t h, size_t d, std::vector&& data, size_t mipLevel = 0) override { size_t expectedSize = w * h * d * GalFormatGetSize(format); if (data.size() != expectedSize) throw std::logic_error("Size of data is not valid for this texture"); oglState.BindTexture(type, texture); switch (type) { case GL_TEXTURE_1D: case GL_PROXY_TEXTURE_1D: break; case GL_TEXTURE_2D: case GL_PROXY_TEXTURE_2D: case GL_TEXTURE_1D_ARRAY: case GL_PROXY_TEXTURE_1D_ARRAY: case GL_TEXTURE_RECTANGLE: case GL_PROXY_TEXTURE_RECTANGLE: case GL_TEXTURE_CUBE_MAP_POSITIVE_X: case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: case GL_PROXY_TEXTURE_CUBE_MAP: glTexSubImage2D(type, mipLevel, x, y, w, h, GalFormatGetGlFormat(format), GalFormatGetGlType(format), data.data()); break; case GL_TEXTURE_3D: case GL_PROXY_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: case GL_PROXY_TEXTURE_2D_ARRAY: glTexSubImage3D(type, mipLevel, x, y, z, w, h, d, GalFormatGetGlFormat(format), GalFormatGetGlType(format), data.data()); break; default: throw std::runtime_error("Unknown texture type"); } glCheckError(); oglState.BindTexture(type, 0); } }; struct FramebufferOgl : public Framebuffer { size_t vpX = 0, vpY = 0, vpW = 1, vpH = 1; std::shared_ptr depthStencil; std::vector> colors; std::vector attachments; GlResource fbo; virtual void Clear() override { oglState.BindFbo(fbo ? fbo : 0); GLbitfield clearBits = 0; clearBits |= GL_COLOR_BUFFER_BIT; clearBits |= GL_DEPTH_BUFFER_BIT; if (depthStencil) clearBits |= GL_STENCIL_BUFFER_BIT; glClear(clearBits); glCheckError(); } virtual void SetViewport(size_t x, size_t y, size_t w, size_t h) override { vpX = x; vpY = y; vpW = w; vpH = h; } }; struct FramebufferConfigOgl : public FramebufferConfig { std::shared_ptr depthStencil; std::map> colors; virtual void SetDepthStencil(std::shared_ptr texture) override { auto tex = std::static_pointer_cast(texture); depthStencil = tex; } virtual void SetTexture(size_t location, std::shared_ptr texture) override { auto tex = std::static_pointer_cast(texture); colors.try_emplace(location, tex); } }; struct ShaderParametersBufferOgl : public ShaderParametersBuffer { std::shared_ptr buffer; bool dirty = true; std::vector data; virtual std::byte* GetDataPtr() override { dirty = true; return data.data(); } virtual void Resize(size_t newSize) override { dirty = true; data.resize(newSize); } }; struct PipelineConfigOgl : public PipelineConfig { std::shared_ptr vertexShader, pixelShader; std::map> textures; std::map shaderParameters; std::shared_ptr targetFb; std::vector> vertexBuffers; Primitive vertexPrimitive = Primitive::Triangle; Blending blending = Blending::Opaque; virtual void SetVertexShader(std::shared_ptr shader) override { vertexShader = std::static_pointer_cast(shader); } virtual void SetPixelShader(std::shared_ptr shader) override { pixelShader = std::static_pointer_cast(shader); } virtual void AddShaderParameter(std::string_view name, Type type) override { shaderParameters.try_emplace(std::string(name), type); } virtual void AddStaticTexture(std::string_view name, std::shared_ptr texture) override { auto tex = std::static_pointer_cast(texture); textures.try_emplace(std::string(name), tex); } virtual void SetTarget(std::shared_ptr target) override { auto fb = std::static_pointer_cast(target); targetFb = fb; } virtual void SetPrimitive(Primitive primitive) override { vertexPrimitive = primitive; } virtual void SetBlending(Blending blendingMode) override { blending = blendingMode; } virtual std::shared_ptr BindVertexBuffer(std::vector &&bufferLayout) override { auto binding = std::make_shared(vertexBuffers.size()); vertexBuffers.push_back(bufferLayout); return std::static_pointer_cast(binding); } virtual std::shared_ptr BindIndexBuffer() override { auto binding = std::make_shared(BufferBindingOgl::indexValue); return std::static_pointer_cast(binding); } }; struct PipelineInstanceOgl : public PipelineInstance { GlResource vao; bool useIndex = false; Primitive primitive = Primitive::Triangle; size_t instances = 0; virtual void Activate() override { oglState.BindVao(vao); } virtual void Render(size_t offset = 0, size_t count = -1) override { GLenum vertexMode; switch (primitive) { case Primitive::Line: vertexMode = GL_LINES; break; case Primitive::Triangle: vertexMode = GL_TRIANGLES; break; case Primitive::TriangleFan: vertexMode = GL_TRIANGLE_FAN; break; case Primitive::TriangleStrip: vertexMode = GL_TRIANGLE_STRIP; break; default: vertexMode = GL_TRIANGLES; } if (useIndex) { if (instances) { glDrawElementsInstanced(vertexMode, instances, GL_UNSIGNED_INT, nullptr, instances); } else { glDrawElements(vertexMode, count, GL_UNSIGNED_INT, nullptr); } } else { if (instances) { glDrawArraysInstanced(vertexMode, offset, instances, count); } else { glDrawArrays(vertexMode, offset, count); } } } virtual void SetInstancesCount(size_t count) override { instances = count; } }; struct PipelineOgl : public Pipeline { std::vector> spbs; std::map shaderParameters; std::vector> staticTextures; GlResource program; struct VertexBindingCommand { size_t bufferId; size_t location; GLenum type; size_t count; size_t stride; size_t offset; size_t instances; }; std::vector vertexBindCmds; Primitive primitive; Blending blending; std::shared_ptr target; virtual void Activate() override { oglState.UseProgram(program); oglState.BindFbo(target->fbo); oglState.SetViewport(target->vpX, target->vpY, target->vpW, target->vpH); oglState.EnableBlending(blending == Blending::Additive); if (target->fbo) glDrawBuffers(target->attachments.size(), target->attachments.data()); for (size_t i = 0; i < staticTextures.size(); i++) { oglState.BindTexture(staticTextures[i]->type, staticTextures[i]->texture, i); } for (auto& spb : spbs) { if (spb->dirty) { spb->buffer->SetData(std::vector(spb->data)); spb->dirty = false; } } } virtual void SetDynamicTexture(std::string_view name, std::shared_ptr texture) override { Activate(); auto tex = std::static_pointer_cast(texture); oglState.BindTexture(tex->type, tex->texture, staticTextures.size()); SetShaderParameter(name, static_cast(staticTextures.size())); glCheckError(); } virtual std::shared_ptr CreateInstance(std::vector, std::shared_ptr>>&& buffers) override { auto instance = std::make_shared(); instance->primitive = primitive; size_t indexBuffer = BufferBindingOgl::indexValue; std::map bufferBindingId; for (auto&& [binding, buffer] : buffers) { auto bind = std::static_pointer_cast(binding); auto buff = std::static_pointer_cast(buffer); if (bind->bufferId == BufferBindingOgl::indexValue) indexBuffer = buff->vbo; else bufferBindingId.try_emplace(bind->bufferId, buff->vbo); } GLuint newVao; glGenVertexArrays(1, &newVao); instance->vao = GlResource(newVao, GlResourceType::Vao); oglState.BindVao(instance->vao); for (const auto& cmd : vertexBindCmds) { auto it = bufferBindingId.find(cmd.bufferId); if (it == bufferBindingId.end()) continue; oglState.BindVbo(it->second); switch (cmd.type) { case GL_FLOAT: case GL_DOUBLE: glVertexAttribPointer(cmd.location, cmd.count, cmd.type, GL_FALSE, cmd.offset, reinterpret_cast(cmd.stride)); break; case GL_UNSIGNED_BYTE: case GL_BYTE: case GL_UNSIGNED_SHORT: case GL_SHORT: case GL_UNSIGNED_INT: case GL_INT: glVertexAttribIPointer(cmd.location, cmd.count, cmd.type, cmd.offset, reinterpret_cast(cmd.stride)); break; } glEnableVertexAttribArray(cmd.location); if (cmd.instances) { glVertexAttribDivisor(cmd.location, cmd.instances); } } if (indexBuffer != BufferBindingOgl::indexValue) { oglState.BindEbo(indexBuffer); instance->useIndex = true; } glCheckError(); oglState.BindVao(0); oglState.BindVbo(0); oglState.BindEbo(0); return instance; } virtual void SetShaderParameter(std::string_view name, float value) override { Activate(); glUniform1f(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, double value) override { Activate(); glUniform1d(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, int8_t value) override { Activate(); glUniform1i(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, int16_t value) override { Activate(); glUniform1i(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, int32_t value) override { Activate(); glUniform1i(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, uint8_t value) override { Activate(); glUniform1ui(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, uint16_t value) override { Activate(); glUniform1ui(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, uint32_t value) override { Activate(); glUniform1ui(shaderParameters.at(std::string(name)), value); } virtual void SetShaderParameter(std::string_view name, glm::vec2 value) override { Activate(); glUniform2f(shaderParameters.at(std::string(name)), value.x, value.y); } virtual void SetShaderParameter(std::string_view name, glm::uvec2 value) override { Activate(); glUniform2ui(shaderParameters.at(std::string(name)), value.x, value.y); } virtual void SetShaderParameter(std::string_view name, glm::vec3 value) override { Activate(); glUniform3f(shaderParameters.at(std::string(name)), value.x, value.y, value.z); } virtual void SetShaderParameter(std::string_view name, glm::vec4 value) override { Activate(); glUniform4f(shaderParameters.at(std::string(name)), value.x, value.y, value.z, value.w); } virtual void SetShaderParameter(std::string_view name, glm::mat4 value) override { Activate(); glUniformMatrix4fv(shaderParameters.at(std::string(name)), 1, GL_FALSE, glm::value_ptr(value)); } }; struct ImplOgl : public Impl { virtual void Init() override { LOG(INFO) << "Initalizing Gal:OpenGL..."; LOG(INFO) << "Initializing GLEW"; glewExperimental = GL_TRUE; GLenum glewStatus = glewInit(); if (glewStatus != GLEW_OK) { LOG(FATAL) << "Failed to initialize GLEW: " << glewGetErrorString(glewStatus); } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glFrontFace(GL_CCW); oglState.EnableBlending(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glCheckError(); if (glActiveTexture == nullptr) { throw std::runtime_error("GLEW initialization failed with unknown reason"); } glPixelStorei(GL_UNPACK_ALIGNMENT, 1); GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutput, nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); } glCheckError(); } virtual void DeInit() override { LOG(INFO) << "Destroying Gal:OpenGL..."; glCheckError(); } virtual void Cleanup() override { } virtual void SetScissor(size_t x = 0, size_t y = 0, size_t width = 0, size_t height = 0) override { glEnable(GL_SCISSOR_TEST); glScissor(x, y, width, height); glCheckError(); } virtual void SetScissor(bool enabled) override { if (enabled) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); glCheckError(); } virtual void SetWireframe(bool enabled) override { glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL); } virtual std::shared_ptr CreateBuffer() override { auto buff = std::make_shared(); GLuint newVbo; glGenBuffers(1, &newVbo); buff->vbo = GlResource(newVbo, GlResourceType::Vbo); buff->SetData({}); glCheckError(); return std::static_pointer_cast(buff); } virtual std::shared_ptr CreateTexture2DConfig(size_t width, size_t height, Format format) override { auto config = std::make_shared(); config->type = GL_TEXTURE_2D; config->width = width; config->height = height; config->depth = 1; config->format = format; return std::static_pointer_cast(config); } virtual std::shared_ptr CreateTexture3DConfig(size_t width, size_t height, size_t depth, bool interpolateLayers, Format format) override { auto config = std::make_shared(); config->type = interpolateLayers ? GL_TEXTURE_3D : GL_TEXTURE_2D_ARRAY; config->width = width; config->height = height; config->depth = depth; config->interpolateLayers = interpolateLayers; config->format = format; return std::static_pointer_cast(config); } virtual std::shared_ptr BuildTexture(std::shared_ptr config) override { auto texConfig = std::static_pointer_cast(config); auto texture = std::make_shared(); texture->type = texConfig->type; texture->format = texConfig->format; texture->width = texConfig->width; texture->height = texConfig->height; texture->depth = texConfig->depth; texture->linear = texConfig->linear; GLuint newTex; glGenTextures(1, &newTex); texture->texture = GlResource(newTex, GlResourceType::Texture); oglState.BindTexture(texture->type, texture->texture); glTexParameteri(texture->type, GL_TEXTURE_MIN_FILTER, GalFilteringGetGlType(texConfig->min)); glTexParameteri(texture->type, GL_TEXTURE_MAG_FILTER, GalFilteringGetGlType(texConfig->max)); glTexParameteri(texture->type, GL_TEXTURE_WRAP_S, GalWrappingGetGlType(texConfig->wrap)); glTexParameteri(texture->type, GL_TEXTURE_WRAP_T, GalWrappingGetGlType(texConfig->wrap)); oglState.BindTexture(texture->type, 0); texture->SetData(std::vector(texture->width * texture->height * texture->depth * GalFormatGetSize(texture->format))); glCheckError(); return std::static_pointer_cast(texture); } virtual std::shared_ptr CreatePipelineConfig() override { auto pipelineConfig = std::make_shared(); return std::static_pointer_cast(pipelineConfig); } virtual std::shared_ptr BuildPipeline(std::shared_ptr pipelineConfig) override { auto pipeline = std::make_shared(); auto config = std::static_pointer_cast(pipelineConfig); pipeline->primitive = config->vertexPrimitive; pipeline->blending = config->blending; pipeline->target = config->targetFb; if (!pipeline->target) pipeline->target = std::static_pointer_cast(GetDefaultFramebuffer()); /* * Shader compilation */ bool vertexFailed = false, pixelFailed = false, linkFailed = false; const GLchar* vertexSourcePtr = config->vertexShader->code.c_str(); const GLchar* pixelSourcePtr = config->pixelShader->code.c_str(); GLuint vertex, pixel; GLint success; GLuint program; GLchar infoLog[512]; vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vertexSourcePtr, NULL); glCompileShader(vertex); glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertex, 512, NULL, infoLog); LOG(ERROR) << "Vertex shader compilation failed: " << std::endl << infoLog; vertexFailed = true; }; pixel = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(pixel, 1, &pixelSourcePtr, NULL); glCompileShader(pixel); glGetShaderiv(pixel, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(pixel, 512, NULL, infoLog); LOG(ERROR) << "Fragment shader compilation failed: " << std::endl << infoLog; pixelFailed = true; }; if (vertexFailed || pixelFailed) throw std::runtime_error("Shaders not compiled"); program = glCreateProgram(); glAttachShader(program, vertex); glAttachShader(program, pixel); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(program, 512, NULL, infoLog); LOG(ERROR) << "Shader program not linked: " << std::endl << infoLog; linkFailed = true; } glDeleteShader(vertex); glDeleteShader(pixel); if (linkFailed) throw std::runtime_error("Shader not linked"); oglState.UseProgram(program); pipeline->program = GlResource(program, GlResourceType::Program); glCheckError(); /* * Shader parameters buffers */ constexpr auto spbGlobalsName = "Globals"; size_t spbGlobalsBind = 0; size_t spbIndex = glGetUniformBlockIndex(program, spbGlobalsName); if (spbIndex != GL_INVALID_VALUE) { glUniformBlockBinding(program, spbIndex, spbGlobalsBind); auto spbGlobals = std::static_pointer_cast(GetGlobalShaderParameters()); glBindBufferBase(GL_UNIFORM_BUFFER, spbGlobalsBind, spbGlobals->buffer->vbo); pipeline->spbs.emplace_back(spbGlobals); } else LOG(ERROR) << "Cannot bind Globals UBO to shader. Maybe uniform block Globals missing?"; glCheckError(); /* * Shader parameters */ for (auto&& [name, type] : config->shaderParameters) { GLint location = glGetUniformLocation(program, name.c_str()); if (location < 0) { LOG(ERROR) << "Uniform name \"" << name << "\" not found in shader"; } pipeline->shaderParameters.insert({ name,location }); } glCheckError(); /* * Static textures */ size_t usedTextureBlocks = 0; for (auto&& [name, texture] : config->textures) { GLint location = glGetUniformLocation(program, name.c_str()); if (location < 0) { LOG(ERROR) << "Texture uniform name \"" << name << "\" not found in shader"; } glUniform1i(location, usedTextureBlocks); pipeline->staticTextures.push_back(texture); usedTextureBlocks++; } glCheckError(); /* * Vertex attributes */ size_t bufferId = 0; for (const auto& buffer : config->vertexBuffers) { size_t vertexSize = 0; auto& binds = pipeline->vertexBindCmds; size_t cmdOffset = binds.size(); for (const auto& [name, type, count, instances] : buffer) { if (name.empty()) { vertexSize += GalTypeGetSize(type) * count; continue; } GLint location = glGetAttribLocation(program, name.c_str()); if (location < 0) { LOG(ERROR) << "Vertex attribute name \"" << name << "\" not found in shader"; } size_t attribSize = GalTypeGetSize(type); for (size_t i = 0; i < count; i++) { if (location < 0) { vertexSize += attribSize; continue; } binds.push_back({ bufferId, static_cast(location + i), GalTypeGetComponentGlType(type), GalTypeGetComponents(type), vertexSize, 0, instances, }); vertexSize += attribSize; } } for (size_t i = cmdOffset; i < binds.size(); i++) binds[i].offset = vertexSize; bufferId++; } glCheckError(); return pipeline; } virtual std::shared_ptr CreateFramebufferConfig() override { auto config = std::make_shared(); return std::static_pointer_cast(config); } virtual std::shared_ptr BuildFramebuffer(std::shared_ptr config) override { auto conf = std::static_pointer_cast(config); auto fb = std::make_shared(); GLuint newFbo; glGenFramebuffers(1, &newFbo); fb->fbo = GlResource(newFbo, GlResourceType::Fbo); oglState.BindFbo(fb->fbo); if (conf->depthStencil) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, conf->depthStencil->type, conf->depthStencil->texture, 0); fb->depthStencil = std::move(conf->depthStencil); } for (auto&& [location, texture] : conf->colors) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + location, texture->type, texture->texture, 0); fb->colors.emplace_back(std::move(texture)); fb->attachments.push_back(GL_COLOR_ATTACHMENT0 + location); } if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LOG(ERROR) << "Framebuffer not completed: " << glCheckFramebufferStatus(GL_FRAMEBUFFER); } oglState.BindFbo(0); glCheckError(); return std::static_pointer_cast(fb); } virtual std::shared_ptr GetDefaultFramebuffer() override { if (!fbDefault) { fbDefault = std::make_shared(); fbDefault->fbo = GlResource(0, GlResourceType::None); fbDefault->attachments.push_back(GL_COLOR_ATTACHMENT0); } return std::static_pointer_cast(fbDefault); } virtual std::shared_ptr GetGlobalShaderParameters() override { if (!spbDefault) { spbDefault = std::make_shared(); spbDefault->buffer = std::static_pointer_cast(GetImplementation()->CreateBuffer()); } return spbDefault; } virtual std::shared_ptr LoadVertexShader(std::string_view code) override { auto shader = std::make_shared(); shader->code = code; shader->isVertex = true; return std::static_pointer_cast(shader); } virtual std::shared_ptr LoadPixelShader(std::string_view code) override { auto shader = std::make_shared(); shader->code = code; shader->isVertex = false; return std::static_pointer_cast(shader); } }; Impl* Gal::GetImplementation() { if (!impl) impl = std::make_unique(); return impl.get(); }