/*
* Descent 3
* Copyright (C) 2024 Descent Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "dyna_gl.h"
#include "holder.h"
template
struct VertexAttrib {
using EnclosingType = E;
GLint size;
GLenum type;
GLboolean normalized;
uintptr_t offset;
std::string name;
};
template
VertexAttrib vertexAttrib(GLint size, GLenum type, GLboolean normalized, MemberType EnclosingType::*member, std::string name) {
EnclosingType e;
return VertexAttrib{
size,
type,
normalized,
reinterpret_cast(&(e.*member)) - reinterpret_cast(&e),
std::move(name)
};
}
template
struct VertexBuffer {
VertexBuffer(GLuint program,
std::vector> attribs,
size_t vertexCount,
GLenum bufferType,
V const* initialData = nullptr)
: vao_{outval(dglGenVertexArrays)},
vbo_{outval(dglGenBuffers)} {
dglBindVertexArray(vao_);
bind();
dglBufferData(GL_ARRAY_BUFFER,
vertexCount * sizeof(V),
initialData,
bufferType);
for (std::size_t i{}; i < attribs.size(); i++) {
dglEnableVertexAttribArray(i);
dglVertexAttribPointer(i,
attribs[i].size,
attribs[i].type,
attribs[i].normalized,
sizeof(V),
reinterpret_cast(attribs[i].offset));
dglBindAttribLocation(program, i, attribs[i].name.c_str());
}
}
void UpdateData(size_t vtx_offset, size_t vtx_count, V const* vertices) {
bind();
dglBufferSubData(GL_ARRAY_BUFFER, vtx_offset * sizeof(V), vtx_count * sizeof(V), vertices);
}
protected:
void bind() {
dglBindBuffer(GL_ARRAY_BUFFER, vbo_);
}
private:
static void DeleteBuffer(GLuint id) {
dglDeleteBuffers(1, &id);
}
static void DeleteVertexArray(GLuint id) {
dglDeleteVertexArrays(1, &id);
}
template
GLuint outval(Generator&& gen) {
GLuint id;
gen(1, &id);
return id;
}
MoveOnlyHolder vao_;
MoveOnlyHolder vbo_;
};
// https://www.khronos.org/opengl/wiki/Buffer_Object_Streaming#Buffer_update
template
struct OrphaningVertexBuffer : VertexBuffer {
OrphaningVertexBuffer(GLuint program, std::vector> attribs)
: VertexBuffer{program, std::move(attribs), kVertexCount, kBufferType} {}
template ::value_type, V>>>
size_t AddVertexData(VertexIter begin, VertexIter end) {
this->bind();
auto dist = std::distance(begin, end);
if (nextVertex_ + dist >= kVertexCount) {
dglBufferData(GL_ARRAY_BUFFER,
kVertexCount * sizeof(V),
nullptr,
kBufferType);
nextVertex_ = 0;
}
auto start = nextVertex_;
V* mapped = reinterpret_cast(dglMapBufferRange(GL_ARRAY_BUFFER, start * sizeof(V), dist * sizeof(V), GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT));
std::copy(begin, end, mapped);
dglUnmapBuffer(GL_ARRAY_BUFFER);
nextVertex_ += dist;
return start;
}
private:
static constexpr size_t kVertexCount{1 << 16};
static constexpr GLenum kBufferType{GL_STREAM_DRAW};
size_t nextVertex_{};
};
template
struct Shader {
static_assert(kType == GL_VERTEX_SHADER || kType == GL_FRAGMENT_SHADER);
explicit Shader(std::string_view src)
: id_{dglCreateShader(kType)} {
if (id_ == 0) {
throw std::runtime_error("failed to create shader");
}
char const* srcptr = src.data();
GLint srclen = src.size();
dglShaderSource(id_, 1, &srcptr, &srclen);
dglCompileShader(id_);
GLint compile_result;
dglGetShaderiv(id_, GL_COMPILE_STATUS, &compile_result);
if (compile_result != GL_TRUE) {
GLsizei log_length = 0;
GLchar message[1024];
dglGetShaderInfoLog(id_, 1024, &log_length, message);
throw std::runtime_error(std::string{message, static_cast(log_length)});
}
}
GLuint id() const {
ASSERT(id_ != 0);
return id_;
}
private:
static void DeleteShader(GLuint id) {
dglDeleteShader(id);
}
MoveOnlyHolder id_;
};
template
struct ShaderProgram {
explicit ShaderProgram(std::string_view vertexSrc, std::string_view fragmentSrc, std::vector> attribs)
: id_{dglCreateProgram()}, vertex_{vertexSrc}, fragment_{fragmentSrc}, vbo_{id_, std::move(attribs)} {
if (id_ == 0) {
throw std::runtime_error("error creating GL program");
}
dglAttachShader(id_, vertex_.id());
dglAttachShader(id_, fragment_.id());
dglLinkProgram(id_);
GLint link_result;
dglGetProgramiv(id_, GL_LINK_STATUS, &link_result);
if (link_result != GL_TRUE)
{
GLsizei log_length = 0;
GLchar message[1024];
dglGetProgramInfoLog(id_, 1024, &log_length, message);
throw std::runtime_error(std::string{message, static_cast(log_length)});
}
}
void Use() const {
dglUseProgram(id_);
}
void Unuse() const {
dglUseProgram(0);
}
template ::value_type, V>>>
size_t addVertexData(VertexIter begin, VertexIter end) {
return vbo_.AddVertexData(begin, end);
}
void setUniformMat4f(std::string const& name, glm::mat4x4 const& matrix) {
dglUniformMatrix4fv(getUniformId(name), 1, GL_FALSE, glm::value_ptr(matrix));
}
void setUniform1i(std::string const& name, GLint val) {
dglUniform1i(getUniformId(name), val);
}
void setUniform1f(std::string const& name, GLfloat val) {
dglUniform1f(getUniformId(name), val);
}
void setUniform4fv(std::string const& name, GLfloat f0, GLfloat f1, GLfloat f2, GLfloat f3) {
dglUniform4f(getUniformId(name), f0, f1, f2, f3);
}
private:
GLint getUniformId(std::string const& name) {
auto it = uniform_cache_.find(name);
if (it != uniform_cache_.end()) {
return it->second;
}
it = uniform_cache_.emplace(name, dglGetUniformLocation(id_, name.c_str())).first;
if (it->second != -1) {
return it->second;
}
throw std::runtime_error("uniform " + name + " nonexistent or inactive");
}
static void DeleteProgram(GLuint id) {
dglDeleteProgram(id);
}
MoveOnlyHolder id_;
Shader vertex_;
Shader fragment_;
OrphaningVertexBuffer vbo_;
std::unordered_map uniform_cache_;
};