nf7/file/gl_obj.cc

1165 lines
36 KiB
C++

#include <algorithm>
#include <array>
#include <cinttypes>
#include <numeric>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <typeinfo>
#include <vector>
#include <imgui.h>
#include <implot.h>
#include <magic_enum.hpp>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/array.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/aggregate_promise.hh"
#include "common/dir_item.hh"
#include "common/factory.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/generic_watcher.hh"
#include "common/gl_enum.hh"
#include "common/gl_fence.hh"
#include "common/gl_obj.hh"
#include "common/gui_config.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_enum.hh"
using namespace std::literals;
namespace nf7 {
namespace {
template <typename T>
class ObjBase : public nf7::FileBase,
public nf7::DirItem, public nf7::Node,
public nf7::AsyncFactory<nf7::Mutex::Resource<std::shared_ptr<typename T::Product>>> {
public:
using ThisObjBase = ObjBase<T>;
using Product = std::shared_ptr<typename T::Product>;
using Resource = nf7::Mutex::Resource<Product>;
using ResourceFuture = nf7::Future<Resource>;
using ResourcePromise = typename ResourceFuture::Promise;
using ThisFactory = nf7::AsyncFactory<Resource>;
struct TypeInfo;
static void UpdateTypeTooltip() noexcept {
T::UpdateTypeTooltip();
}
ObjBase(nf7::Env& env, T&& data = {}) noexcept :
nf7::FileBase(TypeInfo::kType, env, {&log_}),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone), life_(*this), log_(*this),
mem_(std::move(data), *this) {
mem_.onRestore = mem_.onCommit = [this]() {
Drop();
};
}
ObjBase(nf7::Deserializer& ar) : ObjBase(ar.env()) {
ar(mem_.data());
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<ThisObjBase>(env, T {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<Lambda>(*this, parent);
}
std::span<const std::string> GetInputs() const noexcept override {
return T::kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return T::kOutputs;
}
ResourceFuture Create() noexcept final {
return Create(false);
}
ResourceFuture Create(bool ex) noexcept {
auto ctx = std::make_shared<nf7::GenericContext>(*this, "OpenGL obj factory");
ResourcePromise pro {ctx};
mtx_.AcquireLock(ctx, ex).ThenIf([this, ctx, pro](auto& k) mutable {
if (!fu_) {
watcher_.emplace(env());
fu_ = mem_->Create(ctx);
watcher_->AddHandler(nf7::File::Event::kUpdate, [this](auto&) { Drop(); });
}
fu_->Chain(pro, [k](auto& obj) { return Resource {k, obj}; });
});
return pro.future().
template Catch<nf7::Exception>(ctx, [this](auto& e) {
log_.Error(e);
});
}
void UpdateMenu() noexcept override {
if (ImGui::BeginMenu("object management")) {
if (ImGui::MenuItem("create", nullptr, false, !fu_)) {
Create();
}
if (ImGui::MenuItem("drop", nullptr, false, !!fu_)) {
Drop();
}
if (ImGui::MenuItem("drop and create")) {
Drop();
Create();
}
ImGui::EndMenu();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("these actions can cause CORRUPTION of running lambdas");
}
if constexpr (nf7::gui::ConfigData<T>) {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
}
}
void UpdateTooltip() noexcept override {
const char* status = "(unknown)";
if (fu_) {
if (fu_->done()) {
status = "ready";
} else if (fu_->error()) {
status = "error";
} else {
status = "creating";
}
} else {
status = "unused";
}
ImGui::Text("status: %s", status);
ImGui::Spacing();
const auto prod = fu_ && fu_->done()? fu_->value(): nullptr;
mem_->UpdateTooltip(prod);
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node, ThisFactory>(t).Select(this, &mem_);
}
private:
nf7::Life<ThisObjBase> life_;
nf7::LoggerRef log_;
std::optional<nf7::GenericWatcher> watcher_;
nf7::Mutex mtx_;
std::optional<nf7::Future<Product>> fu_;
nf7::GenericMemento<T> mem_;
void Drop() noexcept {
auto ctx = std::make_shared<nf7::GenericContext>(*this, "dropping OpenGL obj");
mtx_.AcquireLock(ctx, true /* = exclusive */).
ThenIf([this](auto&) {
fu_ = std::nullopt;
Touch();
});
}
class Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<ThisObjBase::Lambda> {
public:
Lambda(ThisObjBase& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept final {
if (!f_) return;
f_->Create(true /* = exclusive */).
ThenIf(shared_from_this(), [this, in](auto& obj) {
try {
if (f_ && f_->mem_->Handle(shared_from_this(), obj, in)) {
f_->Touch();
}
} catch (nf7::Exception& e) {
f_->log_.Error(e);
}
});
}
private:
using std::enable_shared_from_this<Lambda>::shared_from_this;
nf7::Life<ThisObjBase>::Ref f_;
};
};
struct Buffer {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL buffer");
}
static inline const std::vector<std::string> kInputs = {
"upload",
};
static inline const std::vector<std::string> kOutputs = {
};
using Product = nf7::gl::Buffer;
Buffer() = default;
Buffer(const Buffer&) = default;
Buffer(Buffer&&) = default;
Buffer& operator=(const Buffer&) = default;
Buffer& operator=(Buffer&&) = default;
void serialize(auto& ar) {
ar(target_, usage_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "target";
st << YAML::Value << std::string {magic_enum::enum_name(target_)};
st << YAML::Key << "usage";
st << YAML::Value << std::string {magic_enum::enum_name(usage_)};
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
const auto target = magic_enum::
enum_cast<gl::BufferTarget>(yaml["target"].as<std::string>()).value();
const auto usage = magic_enum::
enum_cast<gl::BufferUsage>(yaml["usage"].as<std::string>()).value();
target_ = target;
usage_ = usage;
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum"};
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(const std::shared_ptr<nf7::Context>& ctx) noexcept {
return Product::Create(ctx, gl::ToEnum(target_));
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>& handler,
const nf7::Mutex::Resource<std::shared_ptr<Product>>& res,
const nf7::Node::Lambda::Msg& in) {
if (in.name == "upload") {
const auto& vec = in.value.vector();
if (vec->size() == 0) return false;
const auto usage = gl::ToEnum(usage_);
handler->env().ExecGL(handler, [res, vec, usage]() {
const auto n = static_cast<GLsizeiptr>(vec->size());
auto& buf = **res;
auto& m = buf.meta();
glBindBuffer(m.type, buf.id());
{
if (m.size != vec->size()) {
m.size = vec->size();
glBufferData(m.type, n, vec->data(), usage);
} else {
glBufferSubData(m.type, 0, n, vec->data());
}
}
glBindBuffer(m.type, 0);
assert(0 == glGetError());
});
return true;
} else {
throw nf7::Exception {"unknown input: "+in.name};
}
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
const auto t = magic_enum::enum_name(target_);
ImGui::Text("target: %.*s", static_cast<int>(t.size()), t.data());
if (prod) {
ImGui::Spacing();
ImGui::Text(" id: %zu", static_cast<size_t>(prod->id()));
ImGui::Text("size: %zu bytes", prod->meta().size);
}
}
private:
gl::BufferTarget target_ = gl::BufferTarget::Array;
gl::BufferUsage usage_ = gl::BufferUsage::StaticDraw;
};
template <>
struct ObjBase<Buffer>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Buffer>> kType = {"GL/Buffer", {"nf7::DirItem"}};
};
struct Texture {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL texture");
}
static inline const std::vector<std::string> kInputs = {
"upload", "download",
};
static inline const std::vector<std::string> kOutputs = {
"buffer",
};
using Product = nf7::gl::Texture;
Texture() = default;
Texture(const Texture&) = default;
Texture(Texture&&) = default;
Texture& operator=(const Texture&) = default;
Texture& operator=(Texture&&) = default;
void serialize(auto& ar) {
ar(target_, numtype_, comp_, size_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "target";
st << YAML::Value << std::string {magic_enum::enum_name(target_)};
st << YAML::Key << "numtype";
st << YAML::Value << std::string {magic_enum::enum_name(numtype_)};
st << YAML::Key << "comp";
st << YAML::Value << std::string {magic_enum::enum_name(comp_)};
st << YAML::Key << "size";
st << YAML::Value << YAML::Flow;
st << YAML::BeginSeq;
st << size_[0];
st << size_[1];
st << size_[2];
st << YAML::EndSeq;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
const auto target = magic_enum::
enum_cast<gl::TextureTarget>(yaml["target"].as<std::string>()).value();
const auto numtype = magic_enum::
enum_cast<gl::NumericType>(yaml["numtype"].as<std::string>()).value();
const auto comp = magic_enum::
enum_cast<gl::ColorComp>(yaml["comp"].as<std::string>()).value();
const auto size = yaml["size"].as<std::vector<uint32_t>>();
const auto dim = gl::GetDimension(target);
const auto itr = std::find(size.begin(), size.end(), 0);
if (dim > std::distance(size.begin(), itr)) {
throw nf7::Exception {"invalid size specification"};
}
target_ = target;
numtype_ = numtype;
comp_ = comp;
std::copy(size.begin(), size.begin()+dim, size_.begin());
std::fill(size_.begin()+dim, size_.end(), 1);
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum"};
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(const std::shared_ptr<nf7::Context>& ctx) noexcept
try {
std::array<GLsizei, 3> size;
std::transform(size_.begin(), size_.end(), size.begin(),
[](auto x) { return static_cast<GLsizei>(x); });
// FIXME cast is unnecessary
return Product::Create(
ctx, gl::ToEnum(target_), static_cast<GLint>(gl::ToInternalFormat(numtype_, comp_)), size);
} catch (nf7::Exception&) {
return {std::current_exception()};
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>& handler,
const nf7::Mutex::Resource<std::shared_ptr<Product>>& res,
const nf7::Node::Lambda::Msg& in) {
if (in.name == "upload") {
const auto& v = in.value;
const auto vec = v.tuple("vec").vector();
auto& tex = **res;
static const char* kOffsetNames[] = {"x", "y", "z"};
static const char* kSizeNames[] = {"w", "h", "d"};
std::array<uint32_t, 3> offset = {0};
std::array<uint32_t, 3> size = {1, 1, 1};
const auto dim = gl::GetDimension(target_);
for (size_t i = 0; i < dim; ++i) {
offset[i] = v.tupleOr(kOffsetNames[i], nf7::Value::Integer {0}).integer<uint32_t>();
size[i] = v.tuple(kSizeNames[i]).integer<uint32_t>();
if (size[i] == 0) {
return false;
}
if (offset[i]+size[i] > size_[i]) {
throw nf7::Exception {"texture size overflow"};
}
}
const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {});
const auto vecsz = texel*gl::GetCompCount(comp_)*gl::GetByteSize(numtype_);
if (vec->size() < static_cast<size_t>(vecsz)) {
throw nf7::Exception {"vector is too small"};
}
const auto fmt = gl::ToEnum(comp_);
const auto type = gl::ToEnum(numtype_);
handler->env().ExecGL(handler, [=, &tex]() {
const auto target = tex.meta().type;
glBindTexture(target, tex.id());
switch (target) {
case GL_TEXTURE_2D:
case GL_TEXTURE_RECTANGLE:
glTexSubImage2D(target, 0,
static_cast<GLint>(offset[0]),
static_cast<GLint>(offset[1]),
static_cast<GLsizei>(size[0]),
static_cast<GLsizei>(size[1]),
fmt, type, vec->data());
break;
default:
assert(false);
break;
}
glBindTexture(target, 0);
assert(0 == glGetError());
});
return true;
} else if (in.name == "download") {
const auto numtype = magic_enum::
enum_cast<gl::NumericType>(in.value.tuple("numtype").string()).value_or(numtype_);
const auto comp = magic_enum::
enum_cast<gl::ColorComp>(in.value.tuple("comp").string()).value_or(comp_);
handler->env().ExecGL(handler, [=]() {
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
const auto& tex = **res;
const auto size = tex.meta().size;
const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {});
const auto bsize = static_cast<size_t>(texel)*GetCompCount(comp)*GetByteSize(numtype);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(bsize), nullptr, GL_DYNAMIC_READ);
const auto target = tex.meta().type;
glBindTexture(target, tex.id());
glGetTexImage(target, 0, gl::ToEnum(comp), gl::ToEnum(numtype), nullptr);
glBindTexture(target, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
assert(0 == glGetError());
nf7::gl::ExecFenceSync(handler).ThenIf([=, &tex](auto&) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
auto buf = std::make_shared<std::vector<uint8_t>>(bsize);
const auto ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
std::memcpy(buf->data(), ptr, bsize);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteBuffers(1, &pbo);
assert(0 == glGetError());
handler->env().ExecSub(handler, [=, &tex]() {
auto v = nf7::Value {std::vector<nf7::Value::TuplePair> {
{"w", static_cast<nf7::Value::Integer>(size[0])},
{"h", static_cast<nf7::Value::Integer>(size[1])},
{"d", static_cast<nf7::Value::Integer>(size[2])},
{"vector", buf},
}};
in.sender->Handle("buffer", std::move(v), handler);
});
});
});
return false;
} else {
throw nf7::Exception {"unknown input: "+in.name};
}
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
const auto t = magic_enum::enum_name(target_);
ImGui::Text("target : %.*s", static_cast<int>(t.size()), t.data());
const auto f = magic_enum::enum_name(numtype_);
ImGui::Text("numtype: %.*s", static_cast<int>(f.size()), f.data());
const auto c = magic_enum::enum_name(comp_);
ImGui::Text("comp : %.*s (%" PRIu8 " values)",
static_cast<int>(c.size()), c.data(),
magic_enum::enum_integer(comp_));
ImGui::Text("size : %" PRIu32 " x %" PRIu32 " x %" PRIu32, size_[0], size_[1], size_[2]);
ImGui::Spacing();
if (prod) {
const auto id = static_cast<intptr_t>(prod->id());
const auto& m = prod->meta();
ImGui::Text("id: %" PRIiPTR, id);
if (m.type == GL_TEXTURE_2D) {
ImGui::Spacing();
ImGui::TextUnformatted("preview:");
ImGui::Image(reinterpret_cast<void*>(id),
ImVec2 {static_cast<float>(size_[0]), static_cast<float>(size_[1])});
}
}
}
private:
gl::TextureTarget target_ = gl::TextureTarget::Rect;
gl::NumericType numtype_ = gl::NumericType::U8;
gl::ColorComp comp_ = gl::ColorComp::RGBA;
std::array<uint32_t, 3> size_ = {256, 256, 1};
};
template <>
struct ObjBase<Texture>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Texture>> kType = {"GL/Texture", {"nf7::DirItem"}};
};
struct Shader {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL shader");
}
static inline const std::vector<std::string> kInputs = {};
static inline const std::vector<std::string> kOutputs = {};
using Product = nf7::gl::Shader;
Shader() = default;
Shader(const Shader&) = default;
Shader(Shader&&) = default;
Shader& operator=(const Shader&) = default;
Shader& operator=(Shader&&) = default;
void serialize(auto& ar) {
ar(type_, src_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "type";
st << YAML::Value << std::string {magic_enum::enum_name(type_)};
st << YAML::Key << "src";
st << YAML::Value << YAML::Literal << src_;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
const auto type = magic_enum::
enum_cast<gl::ShaderType>(yaml["type"].as<std::string>()).value();
auto src = yaml["src"].as<std::string>();
type_ = type;
src_ = std::move(src);
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum"};
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(
const std::shared_ptr<nf7::Context>& ctx) noexcept {
// TODO: preprocessing GLSL source
return Product::Create(ctx, gl::ToEnum(type_), src_);
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>&,
const nf7::Mutex::Resource<std::shared_ptr<Product>>&,
const nf7::Node::Lambda::Msg&) {
return false;
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
const auto t = magic_enum::enum_name(type_);
ImGui::Text("type: %.*s", static_cast<int>(t.size()), t.data());
if (prod) {
ImGui::Text("id : %zu", static_cast<size_t>(prod->id()));
}
}
private:
gl::ShaderType type_;
std::string src_;
};
template <>
struct ObjBase<Shader>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Shader>> kType = {"GL/Shader", {"nf7::DirItem"}};
};
struct Program {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL program");
}
static inline const std::vector<std::string> kInputs = {
"draw",
};
static inline const std::vector<std::string> kOutputs = {
"done",
};
using Product = nf7::gl::Program;
Program() = default;
Program(const Program&) = default;
Program(Program&&) = default;
Program& operator=(const Program&) = default;
Program& operator=(Program&&) = default;
void serialize(auto& ar) {
ar(shaders_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "shaders";
st << YAML::Value << YAML::BeginSeq;
for (const auto& shader : shaders_) {
st << shader.Stringify();
}
st << YAML::EndSeq;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
std::vector<nf7::File::Path> shaders;
for (const auto& shader : yaml["shaders"]) {
shaders.push_back(
nf7::File::Path::Parse(shader.as<std::string>()));
}
if (shaders.size() == 0) {
throw nf7::Exception {"no shader is attached"};
}
shaders_ = std::move(shaders);
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(const std::shared_ptr<nf7::Context>& ctx) noexcept
try {
auto& base = ctx->env().GetFileOrThrow(ctx->initiator());
std::vector<nf7::File::Id> shaders;
for (const auto& path : shaders_) {
shaders.push_back(base.ResolveOrThrow(path).id());
}
return Product::Create(ctx, shaders);
} catch (nf7::Exception&) {
return {std::current_exception()};
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>& la,
const nf7::Mutex::Resource<std::shared_ptr<Product>>& prog,
const nf7::Node::Lambda::Msg& msg) {
const auto& base = la->env().GetFileOrThrow(la->initiator());
const auto& v = msg.value;
if (msg.name == "draw") {
const auto mode = gl::ToEnum<gl::DrawMode>(msg.value.tuple("mode").string());
const auto count = v.tuple("count").integer<GLsizei>();
const auto inst = v.tupleOr("instance", nf7::Value::Integer{1}).integer<GLsizei>();
const auto uni = msg.value.tupleOr("uniform", nf7::Value::Tuple {});
uni.tuple();
const auto& vp = msg.value.tuple("viewport");
const auto vp_x = vp.tuple(0).integerOrScalar<GLint>();
const auto vp_y = vp.tuple(1).integerOrScalar<GLint>();
const auto vp_w = vp.tuple(2).integerOrScalar<GLsizei>();
const auto vp_h = vp.tuple(3).integerOrScalar<GLsizei>();
if (vp_w < 0 || vp_h < 0) {
throw nf7::Exception {"negative size viewport"};
}
// this will be triggered when all preparation done
nf7::AggregatePromise apro {la};
// find, fetch and lock FBO
std::optional<nf7::gl::FramebufferFactory::Product> fbo_fu;
std::optional<nf7::gl::Framebuffer::Meta::LockedAttachmentsFuture> fbo_lock_fu;
{
fbo_fu = base.
ResolveOrThrow(v.tuple("fbo").string()).
interfaceOrThrow<nf7::gl::FramebufferFactory>().Create();
nf7::gl::Framebuffer::Meta::LockedAttachmentsFuture::Promise fbo_lock_pro;
fbo_fu->ThenIf([la, fbo_lock_pro](auto& fbo) mutable {
(**fbo).meta().LockAttachments(la).Chain(fbo_lock_pro);
});
fbo_lock_fu = fbo_lock_pro.future();
apro.Add(*fbo_lock_fu);
}
// find, fetch and lock VAO
std::optional<nf7::gl::VertexArrayFactory::Product> vao_fu;
std::optional<nf7::gl::VertexArray::Meta::LockedBuffersFuture> vao_lock_fu;
{
vao_fu = base.
ResolveOrThrow(v.tuple("vao").string()).
interfaceOrThrow<nf7::gl::VertexArrayFactory>().Create();
nf7::gl::VertexArray::Meta::LockedBuffersFuture::Promise vao_lock_pro;
vao_fu->ThenIf([la, vao_lock_pro](auto& vao) mutable {
(**vao).meta().LockBuffers(la).Chain(vao_lock_pro);
});
vao_lock_fu = vao_lock_pro.future();
apro.Add(*vao_lock_fu);
}
// TODO: find, fetch and lock textures
// execute drawing after successful locking
apro.future().Then(nf7::Env::kGL, la, [=](auto&) {
assert(fbo_lock_fu);
assert(vao_lock_fu);
if (!fbo_lock_fu->done() || !vao_lock_fu->done()) {
// TODO
std::cout << "err" << std::endl;
return;
}
const auto& fbo = *fbo_fu->value();
const auto& vao = *vao_fu->value();
// bind objects
glUseProgram((*prog)->id());
glBindFramebuffer(GL_FRAMEBUFFER, fbo->id());
glBindVertexArray(vao->id());
glViewport(vp_x, vp_y, vp_w, vp_h);
// setup uniforms
for (const auto& p : *uni.tuple()) {
if (!SetUniform((*prog)->id(), p.first.c_str(), p.second)) {
// TODO: warn user that the value is ignored
}
}
// draw
glDrawArraysInstanced(mode, 0, count, inst);
const auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
// unbind all
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glUseProgram(0);
assert(0 == glGetError());
try {
nf7::gl::Framebuffer::Meta::ThrowStatus(status);
} catch (nf7::Exception& e) {
std::cout << e.msg() << std::endl;
}
});
return false;
} else {
throw nf7::Exception {"unknown input: "+msg.name};
}
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
if (prod) {
ImGui::Text("id : %zu", static_cast<size_t>(prod->id()));
}
}
private:
std::vector<nf7::File::Path> shaders_;
static bool SetUniform(GLuint prog, const char* name, const nf7::Value& v) noexcept {
const GLint loc = glGetUniformLocation(prog, name);
if (loc < 0) {
return false;
}
// single integer
try {
glUniform1i(loc, v.integer<GLint>());
return true;
} catch (nf7::Exception&) {
}
// single float
try {
glUniform1f(loc, v.scalar<GLfloat>());
return true;
} catch (nf7::Exception&) {
}
// 1~4 dim float vector
try {
const auto& tup = *v.tuple();
switch (tup.size()) {
case 1: glUniform1f(loc, tup[0].second.scalar<GLfloat>()); break;
case 2: glUniform2f(loc, tup[0].second.scalar<GLfloat>(),
tup[1].second.scalar<GLfloat>()); break;
case 3: glUniform3f(loc, tup[0].second.scalar<GLfloat>(),
tup[1].second.scalar<GLfloat>(),
tup[2].second.scalar<GLfloat>()); break;
case 4: glUniform4f(loc, tup[0].second.scalar<GLfloat>(),
tup[1].second.scalar<GLfloat>(),
tup[2].second.scalar<GLfloat>(),
tup[3].second.scalar<GLfloat>()); break;
default: throw nf7::Exception {"invalid tuple size (must be 1~4)"};
}
return true;
} catch (nf7::Exception&) {
}
return false;
}
};
template <>
struct ObjBase<Program>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Program>> kType = {"GL/Program", {"nf7::DirItem"}};
};
struct VertexArray {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL Vertex Array Object");
}
static inline const std::vector<std::string> kInputs = {
};
static inline const std::vector<std::string> kOutputs = {
};
using Product = nf7::gl::VertexArray;
struct Attr {
GLuint index = 0;
GLint size = 1;
gl::NumericType type = gl::NumericType::F32;
bool normalize = false;
GLsizei stride = 0;
uint64_t offset = 0;
GLuint divisor = 0;
nf7::File::Path buffer = {};
void serialize(auto& ar) {
ar(index, size, type, normalize, stride, offset, divisor, buffer);
}
const char* Validate() const noexcept {
if (index >= GL_MAX_VERTEX_ATTRIBS) {
return "too huge index";
}
if (size <= 0 || 4 < size) {
return "invalid size (1, 2, 3 or 4 are allowed)";
}
if (stride < 0) {
return "negative stride";
}
if (offset > static_cast<uint64_t>(stride)) {
return "offset overflow";
}
return nullptr;
}
static void Validate(std::span<const Attr> attrs) {
for (auto& attr : attrs) {
if (const auto msg = attr.Validate()) {
throw nf7::Exception {"invalid attribute: "s+msg};
}
}
std::unordered_set<GLuint> idx;
for (auto& attr : attrs) {
const auto [itr, uniq] = idx.insert(attr.index);
(void) itr;
if (!uniq) {
throw nf7::Exception {"attribute index duplication"};
}
}
}
};
VertexArray() = default;
VertexArray(const VertexArray&) = default;
VertexArray(VertexArray&&) = default;
VertexArray& operator=(const VertexArray&) = default;
VertexArray& operator=(VertexArray&&) = default;
void serialize(auto& ar) {
ar(attrs_);
Attr::Validate(attrs_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "attrs";
st << YAML::Value << YAML::BeginSeq;
for (const auto& attr : attrs_) {
st << YAML::BeginMap;
st << YAML::Key << "index";
st << YAML::Value << attr.index;
st << YAML::Key << "size";
st << YAML::Value << attr.size;
st << YAML::Key << "type";
st << YAML::Value << std::string {magic_enum::enum_name(attr.type)};
st << YAML::Key << "normalize";
st << YAML::Value << attr.normalize;
st << YAML::Key << "stride";
st << YAML::Value << attr.stride;
st << YAML::Key << "offset";
st << YAML::Value << attr.offset;
st << YAML::Key << "divisor";
st << YAML::Value << attr.divisor;
st << YAML::Key << "buffer";
st << YAML::Value << attr.buffer.Stringify();
st << YAML::EndMap;
}
st << YAML::EndSeq;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
std::vector<Attr> attrs;
for (const auto& attr : yaml["attrs"]) {
attrs.push_back({
.index = attr["index"].as<GLuint>(),
.size = attr["size"].as<GLint>(),
.type = magic_enum::enum_cast<gl::NumericType>(attr["type"].as<std::string>()).value(),
.normalize = attr["normalize"].as<bool>(),
.stride = attr["stride"].as<GLsizei>(),
.offset = attr["offset"].as<uint64_t>(),
.divisor = attr["divisor"].as<GLuint>(),
.buffer = nf7::File::Path::Parse(attr["buffer"].as<std::string>()),
});
}
Attr::Validate(attrs);
attrs_ = std::move(attrs);
} catch (std::bad_optional_access&) {
throw nf7::Exception {std::string {"invalid enum"}};
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(const std::shared_ptr<nf7::Context>& ctx) noexcept
try {
auto& base = ctx->env().GetFileOrThrow(ctx->initiator());
std::vector<Product::Meta::Attr> attrs;
attrs.reserve(attrs_.size());
for (auto& attr : attrs_) {
attrs.push_back({
.buffer = base.ResolveOrThrow(attr.buffer).id(),
.index = attr.index,
.size = attr.size,
.type = gl::ToEnum(attr.type),
.normalize = attr.normalize,
.stride = attr.stride,
.offset = attr.offset,
.divisor = attr.divisor,
});
}
return Product::Create(ctx, std::move(attrs));
} catch (nf7::Exception&) {
return {std::current_exception()};
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>&,
const nf7::Mutex::Resource<std::shared_ptr<Product>>&,
const nf7::Node::Lambda::Msg&) {
return false;
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
if (prod) {
ImGui::Text("id: %zu", static_cast<size_t>(prod->id()));
}
}
private:
std::vector<Attr> attrs_;
};
template <>
struct ObjBase<VertexArray>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<VertexArray>> kType = {"GL/VertexArray", {"nf7::DirItem"}};
};
struct Framebuffer {
public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL Framebuffer Object");
}
static inline const std::vector<std::string> kInputs = {
"clear",
};
static inline const std::vector<std::string> kOutputs = {
};
using Product = nf7::gl::Framebuffer;
struct Attachment {
gl::FramebufferSlot slot;
nf7::File::Path path;
void serialize(auto& ar) {
ar(slot, path);
}
};
Framebuffer() = default;
Framebuffer(const Framebuffer&) = default;
Framebuffer(Framebuffer&&) = default;
Framebuffer& operator=(const Framebuffer&) = default;
Framebuffer& operator=(Framebuffer&&) = default;
void serialize(auto& ar) {
ar(attachments_);
}
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "attachments";
st << YAML::Value << YAML::BeginMap;
for (const auto& attachment : attachments_) {
st << YAML::Key << std::string {magic_enum::enum_name(attachment.slot)};
st << YAML::Value << attachment.path.Stringify();
}
st << YAML::EndMap;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Parse(const std::string& v)
try {
const auto yaml = YAML::Load(v);
const auto& yaml_attachments = yaml["attachments"];
std::vector<Attachment> attachments;
for (auto [slot, name] : magic_enum::enum_entries<gl::FramebufferSlot>()) {
if (const auto& yaml_attachment = yaml_attachments[std::string {name}]) {
attachments.push_back({
.slot = slot,
.path = nf7::File::Path::Parse(yaml_attachment.as<std::string>()),
});
}
}
attachments_ = std::move(attachments);
} catch (std::bad_optional_access&) {
throw nf7::Exception {std::string {"invalid enum"}};
} catch (YAML::Exception& e) {
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
}
nf7::Future<std::shared_ptr<Product>> Create(const std::shared_ptr<nf7::Context>& ctx) noexcept
try {
auto& base = ctx->env().GetFileOrThrow(ctx->initiator());
std::vector<nf7::gl::Framebuffer::Meta::Attachment> attachments;
for (auto& attachment : attachments_) {
nf7::File::Id fid = 0;
if (attachment.path.terms().size() > 0) {
fid = base.ResolveOrThrow(attachment.path).id();
}
attachments.push_back({
.tex = fid,
.slot = gl::ToEnum(attachment.slot),
});
}
return Product::Create(ctx, std::move(attachments));
} catch (nf7::Exception&) {
return {std::current_exception()};
}
bool Handle(const std::shared_ptr<nf7::Node::Lambda>& la,
const nf7::Mutex::Resource<std::shared_ptr<Product>>& fb,
const nf7::Node::Lambda::Msg& msg) {
if (msg.name == "clear") {
(**fb).meta().LockAttachments(la).ThenIf(nf7::Env::kGL, la, [fb](auto&) {
glBindFramebuffer(GL_FRAMEBUFFER, (**fb).id());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
});
return false;
} else {
throw nf7::Exception {"unknown command: "+msg.name};
}
}
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
if (prod) {
ImGui::Text("id: %zu", static_cast<size_t>(prod->id()));
}
}
private:
std::vector<Attachment> attachments_;
};
template <>
struct ObjBase<Framebuffer>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Framebuffer>> kType = {"GL/Framebuffer", {"nf7::DirItem"}};
};
}
} // namespace nf7