mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 11:28:56 +00:00
be59b88b26
Use orphaned "buffer update streaming" to eliminate synchronization delays, due to CPU->GPU latency, causing framerate slowdowns. This sends vertex data to the GPU via partial updates to a buffer and reallocates the buffer once it fills up, ensuring that no synchronization is ever needed. The buffer is sized to balance memory usage vs allocation rate, and the GL driver ensures that "orphaned" buffers are only destroyed when all GL commands using them are retired.
261 lines
7.5 KiB
C++
261 lines
7.5 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <glm/mat4x4.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#include "dyna_gl.h"
|
|
#include "holder.h"
|
|
|
|
template <typename E>
|
|
struct VertexAttrib {
|
|
using EnclosingType = E;
|
|
GLint size;
|
|
GLenum type;
|
|
GLboolean normalized;
|
|
uintptr_t offset;
|
|
std::string name;
|
|
};
|
|
|
|
template <typename EnclosingType, typename MemberType>
|
|
VertexAttrib<EnclosingType> vertexAttrib(GLint size, GLenum type, GLboolean normalized, MemberType EnclosingType::*member, std::string name) {
|
|
EnclosingType e;
|
|
return VertexAttrib<EnclosingType>{
|
|
size,
|
|
type,
|
|
normalized,
|
|
reinterpret_cast<uintptr_t>(&(e.*member)) - reinterpret_cast<uintptr_t>(&e),
|
|
std::move(name)
|
|
};
|
|
}
|
|
|
|
template <typename V>
|
|
struct VertexBuffer {
|
|
VertexBuffer(GLuint program,
|
|
std::vector<VertexAttrib<V>> 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 (GLint i{}; i < attribs.size(); i++) {
|
|
dglEnableVertexAttribArray(i);
|
|
dglVertexAttribPointer(i,
|
|
attribs[i].size,
|
|
attribs[i].type,
|
|
attribs[i].normalized,
|
|
sizeof(V),
|
|
reinterpret_cast<void*>(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 <typename Generator>
|
|
GLuint outval(Generator&& gen) {
|
|
GLuint id;
|
|
gen(1, &id);
|
|
return id;
|
|
}
|
|
|
|
MoveOnlyHolder<GLuint, DeleteVertexArray> vao_;
|
|
MoveOnlyHolder<GLuint, DeleteBuffer> vbo_;
|
|
};
|
|
|
|
// https://www.khronos.org/opengl/wiki/Buffer_Object_Streaming#Buffer_update
|
|
template <typename V>
|
|
struct OrphaningVertexBuffer : VertexBuffer<V> {
|
|
OrphaningVertexBuffer(GLuint program, std::vector<VertexAttrib<V>> attribs)
|
|
: VertexBuffer<V>{program, std::move(attribs), kVertexCount, kBufferType} {}
|
|
|
|
template <typename VertexIter,
|
|
typename = std::enable_if<std::is_convertible_v<typename std::iterator_traits<VertexIter>::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<V*>(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 <GLenum kType>
|
|
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<size_t>(log_length)});
|
|
}
|
|
}
|
|
|
|
GLuint id() const {
|
|
ASSERT(id_ != 0);
|
|
return id_;
|
|
}
|
|
|
|
private:
|
|
static void DeleteShader(GLuint id) {
|
|
dglDeleteShader(id);
|
|
}
|
|
MoveOnlyHolder<GLuint, DeleteShader> id_;
|
|
};
|
|
|
|
template <typename V>
|
|
struct ShaderProgram {
|
|
explicit ShaderProgram(std::string_view vertexSrc, std::string_view fragmentSrc, std::vector<VertexAttrib<V>> 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<size_t>(log_length)});
|
|
}
|
|
}
|
|
|
|
void Use() const {
|
|
dglUseProgram(id_);
|
|
}
|
|
|
|
void Unuse() const {
|
|
dglUseProgram(0);
|
|
}
|
|
|
|
template <typename VertexIter,
|
|
typename = std::enable_if<std::is_convertible_v<typename std::iterator_traits<VertexIter>::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<GLuint, DeleteProgram> id_;
|
|
Shader<GL_VERTEX_SHADER> vertex_;
|
|
Shader<GL_FRAGMENT_SHADER> fragment_;
|
|
OrphaningVertexBuffer<V> vbo_;
|
|
std::unordered_map<std::string, GLint> uniform_cache_;
|
|
};
|