nf7/common/gl_obj.cc

458 lines
15 KiB
C++

#include "common/gl_obj.hh"
#include <algorithm>
#include <array>
#include <exception>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <unordered_set>
#include <vector>
#include <tracy/Tracy.hpp>
#include "common/aggregate_promise.hh"
#include "common/factory.hh"
#include "common/future.hh"
#include "common/gl_enum.hh"
#include "common/mutex.hh"
namespace nf7::gl {
template <typename T>
auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
typename T::Factory& factory,
std::function<void(const T&)>&& validator) noexcept {
typename nf7::Future<nf7::Mutex::Resource<std::shared_ptr<T>>>::Promise pro {ctx};
factory.Create().Chain(pro, [validator](auto& v) {
validator(**v);
return v;
});
return pro.future();
}
static auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
nf7::gl::Buffer::Factory& factory,
nf7::gl::BufferTarget target,
size_t required) noexcept {
return LockAndValidate<gl::Buffer>(ctx, factory, [target, required](auto& buf) {
if (buf.meta().target != target) {
throw nf7::Exception {"incompatible buffer target"};
}
const auto size = buf.param().size;
if (size < required) {
std::stringstream st;
st << "buffer shortage (" << size << "/" << required << ")";
throw nf7::Exception {st.str()};
}
});
}
static auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
nf7::gl::Texture::Factory& factory,
nf7::gl::TextureTarget target) noexcept {
return LockAndValidate<gl::Texture>(ctx, factory, [target](auto& tex) {
if (tex.meta().target != target) {
throw nf7::Exception {"incompatible texture target"};
}
});
}
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>> Obj_BufferMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create buffer");
GLuint id;
glGenBuffers(1, &id);
pro.Return(std::make_shared<Obj<Obj_BufferMeta>>(ctx, id, *this));
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>> Obj_TextureMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create texture");
GLuint id;
glGenTextures(1, &id);
const auto t = gl::ToEnum(target);
glBindTexture(t, id);
glTexParameteri(t, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(t, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(t, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(t, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
const auto ifmt = static_cast<GLint>(gl::ToEnum(format));
const GLenum fmt = gl::IsColor(format)? GL_RED: GL_DEPTH_COMPONENT;
switch (gl::GetDimension(target)) {
case 2:
glTexImage2D(t, 0, ifmt, size[0], size[1], 0,
fmt, GL_UNSIGNED_BYTE, nullptr);
break;
default:
assert(false && "unknown texture target");
break;
}
glBindTexture(t, 0);
assert(0 == glGetError());
pro.Return(std::make_shared<Obj<Obj_TextureMeta>>(ctx, id, *this));
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>> Obj_ShaderMeta::Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::string& src) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create shader");
const auto t = gl::ToEnum(type);
const auto id = glCreateShader(t);
if (id == 0) {
pro.Throw<nf7::Exception>("failed to allocate new shader");
return;
}
static const char* kHeader =
"#version 330\n"
"#extension GL_ARB_shading_language_include: require\n";
{
ZoneScopedN("compile");
const GLchar* str[] = {kHeader, src.c_str()};
glShaderSource(id, 2, str, nullptr);
glCompileShader(id);
assert(0 == glGetError());
}
GLint status;
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (status == GL_TRUE) {
pro.Return(std::make_shared<Obj<Obj_ShaderMeta>>(ctx, id, *this));
} else {
GLint len;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &len);
std::string ret(static_cast<size_t>(len), ' ');
glGetShaderInfoLog(id, len, nullptr, ret.data());
pro.Throw<nf7::Exception>(std::move(ret));
}
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>> Obj_ProgramMeta::Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::vector<nf7::File::Id>& shaders) noexcept {
nf7::AggregatePromise apro {ctx};
std::vector<nf7::Future<nf7::Mutex::Resource<std::shared_ptr<gl::Shader>>>> shs;
for (auto shader : shaders) {
shs.emplace_back(ctx->env().GetFileOrThrow(shader).
interfaceOrThrow<nf7::gl::Shader::Factory>().Create());
apro.Add(shs.back());
}
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>>::Promise pro {ctx};
apro.future().Chain(nf7::Env::kGL, ctx, pro, [*this, ctx, shs = std::move(shs)](auto&) {
ZoneScopedN("create program");
// check all shaders
for (auto& sh : shs) { sh.value(); }
// create program
const auto id = glCreateProgram();
if (id == 0) {
throw nf7::Exception {"failed to allocate new program"};
}
// attach shaders
for (auto& sh : shs) {
glAttachShader(id, (*sh.value())->id());
}
{
ZoneScopedN("link");
glLinkProgram(id);
}
// check status
GLint status;
glGetProgramiv(id, GL_LINK_STATUS, &status);
if (status == GL_TRUE) {
return std::make_shared<Obj<Obj_ProgramMeta>>(ctx, id, *this);
} else {
GLint len;
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &len);
std::string ret(static_cast<size_t>(len), ' ');
glGetProgramInfoLog(id, len, nullptr, ret.data());
throw nf7::Exception {std::move(ret)};
}
});
return pro.future();
}
void Obj_ProgramMeta::ApplyState() const noexcept {
if (depth) {
glEnable(GL_DEPTH_TEST);
glDepthRange(depth->near, depth->far);
glDepthFunc(gl::ToEnum(depth->func));
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void Obj_ProgramMeta::RevertState() const noexcept {
glBlendFunc(GL_ONE, GL_ZERO);
glDisable(GL_BLEND);
if (depth) {
glDisable(GL_DEPTH_TEST);
}
}
nf7::Future<std::shared_ptr<Obj<Obj_VertexArrayMeta>>> Obj_VertexArrayMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept
try {
if (index) {
if (index->numtype != gl::NumericType::U8 &&
index->numtype != gl::NumericType::U16 &&
index->numtype != gl::NumericType::U32) {
throw nf7::Exception {"invalid index buffer numtype (only u8/u16/u32 are allowed)"};
}
}
nf7::Future<std::shared_ptr<Obj<Obj_VertexArrayMeta>>>::Promise pro {ctx};
LockAttachments(ctx).Chain(
nf7::Env::kGL, ctx, pro,
[*this, ctx, pro](auto& bufs) mutable {
ZoneScopedN("create va");
// check all buffers
if (index) {
assert(bufs.index);
const auto& m = (***bufs.index).meta();
if (m.target != gl::BufferTarget::ElementArray) {
throw nf7::Exception {"index buffer is not ElementArray"};
}
}
assert(bufs.attrs.size() == attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
if ((**bufs.attrs[i]).meta().target != gl::BufferTarget::Array) {
throw nf7::Exception {"buffer is not Array"};
}
}
GLuint id;
glGenVertexArrays(1, &id);
glBindVertexArray(id);
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
const auto& buf = **bufs.attrs[i];
glBindBuffer(GL_ARRAY_BUFFER, buf.id());
glEnableVertexAttribArray(attr.location);
glVertexAttribDivisor(attr.location, attr.divisor);
glVertexAttribPointer(
attr.location,
attr.size,
gl::ToEnum(attr.type),
attr.normalize,
attr.stride,
reinterpret_cast<GLvoid*>(static_cast<GLintptr>(attr.offset)));
}
if (index) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (***bufs.index).id());
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
assert(0 == glGetError());
return std::make_shared<Obj<Obj_VertexArrayMeta>>(ctx, id, *this);
});
return pro.future();
} catch (nf7::Exception&) {
return {std::current_exception()};
}
Obj_VertexArrayMeta::LockedAttachmentsFuture Obj_VertexArrayMeta::LockAttachments(
const std::shared_ptr<nf7::Context>& ctx,
const ValidationHint& vhint) const noexcept
try {
const auto Lock = [&](nf7::File::Id id, gl::BufferTarget target, size_t req) {
auto& factory = ctx->env().
GetFileOrThrow(id).interfaceOrThrow<gl::Buffer::Factory>();
return LockAndValidate(ctx, factory, target, req);
};
nf7::AggregatePromise apro {ctx};
auto ret = std::make_shared<LockedAttachments>();
LockedAttachmentsFuture::Promise pro {ctx};
// lock array buffers
std::unordered_map<nf7::File::Id, gl::Buffer::Factory::Product> attrs_fu_map;
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
const size_t required =
// when non-instanced and no-index-buffer drawing
attr.divisor == 0 && vhint.vertices > 0 && !index?
static_cast<size_t>(attr.size) * vhint.vertices * gl::GetByteSize(attr.type):
// when instanced drawing
attr.divisor > 0 && vhint.instances > 0?
static_cast<size_t>(attr.stride) * (vhint.instances-1) + attr.offset:
size_t {0};
if (attrs_fu_map.end() == attrs_fu_map.find(attr.buffer)) {
auto [itr, add] = attrs_fu_map.emplace(
attr.buffer, Lock(attr.buffer, gl::BufferTarget::Array, required));
(void) add;
apro.Add(itr->second);
}
}
// serialize attrs_fu_map
std::vector<gl::Buffer::Factory::Product> attrs_fu;
for (const auto& attr : attrs) {
auto itr = attrs_fu_map.find(attr.buffer);
assert(itr != attrs_fu_map.end());
attrs_fu.push_back(itr->second);
}
// lock index buffers (it must be the last element in `fus`)
if (index) {
const auto required = gl::GetByteSize(index->numtype) * vhint.vertices;
apro.Add(Lock(index->buffer, gl::BufferTarget::ElementArray, required).
Chain(pro, [ret](auto& buf) { ret->index = buf; }));
}
// return ret
apro.future().Chain(pro, [ret, attrs_fu = std::move(attrs_fu)](auto&) {
ret->attrs.reserve(attrs_fu.size());
for (auto& fu : attrs_fu) {
ret->attrs.push_back(fu.value());
}
return std::move(*ret);
});
return pro.future();
} catch (nf7::Exception&) {
return { std::current_exception() };
}
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>> Obj_FramebufferMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>>::Promise pro {ctx};
LockAttachments(ctx).
Chain(nf7::Env::kGL, ctx, pro, [ctx, *this](auto& k) mutable {
ZoneScopedN("create fb");
GLuint id;
glGenFramebuffers(1, &id);
glBindFramebuffer(GL_FRAMEBUFFER, id);
for (size_t i = 0; i < colors.size(); ++i) {
if (const auto& tex = k.colors[i]) {
glFramebufferTexture(GL_FRAMEBUFFER,
static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + i),
(***tex).id(),
0 /* = level */);
}
}
if (k.depth) {
glFramebufferTexture(GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
(***k.depth).id(),
0 /* = level */);
}
if (k.stencil) {
glFramebufferTexture(GL_FRAMEBUFFER,
GL_STENCIL_ATTACHMENT,
(***k.stencil).id(),
0 /* = level */);
}
const auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
const auto ret = std::make_shared<Obj<Obj_FramebufferMeta>>(ctx, id, *this);
if (0 != glGetError()) {
throw nf7::Exception {"failed to setup framebuffer"};
}
if (status != GL_FRAMEBUFFER_COMPLETE) {
throw nf7::Exception {"invalid framebuffer status"};
}
return ret;
});
return pro.future();
}
Obj_FramebufferMeta::LockedAttachmentsFuture Obj_FramebufferMeta::LockAttachments(
const std::shared_ptr<nf7::Context>& ctx) const noexcept
try {
auto ret = std::make_shared<LockedAttachments>();
// file duplication check for preventing deadlock by double lock
std::unordered_set<nf7::File::Id> locked;
for (const auto& col : colors) {
if (col && col->tex && !locked.insert(col->tex).second) {
throw nf7::Exception {"attached color texture is duplicated"};
}
}
if (depth && depth->tex && !locked.insert(depth->tex).second) {
throw nf7::Exception {"attached depth texture is duplicated"};
}
if (stencil && stencil->tex && !locked.insert(stencil->tex).second) {
throw nf7::Exception {"attached stencil texture is duplicated"};
}
nf7::AggregatePromise apro {ctx};
LockedAttachmentsFuture::Promise pro {ctx};
const auto Lock = [&](nf7::File::Id id) {
auto& factory = ctx->env().
GetFileOrThrow(id).
interfaceOrThrow<gl::Texture::Factory>();
return LockAndValidate(ctx, factory, gl::TextureTarget::Tex2D);
};
for (size_t i = 0; i < colors.size(); ++i) {
const auto& color = colors[i];
if (color && color->tex) {
apro.Add(Lock(color->tex).Chain(pro, [i, ret](auto& res) {
ret->colors[i] = res;
}));
}
}
if (depth && depth->tex) {
apro.Add(Lock(depth->tex).Chain(pro, [ret](auto& res) {
ret->depth = res;
}));
}
if (stencil && stencil->tex) {
apro.Add(Lock(stencil->tex).Chain(pro, [ret](auto& res) {
ret->stencil = res;
}));
}
apro.future().Chain(pro, [ret](auto&) { return std::move(*ret); });
return pro.future();
} catch (nf7::Exception&) {
return { std::current_exception() };
}
} // namespace nf7::gl