1440 lines
43 KiB
C++
1440 lines
43 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 <imgui_internal.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/optional.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_config.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/gl_shader_preproc.hh"
|
|
#include "common/gui.hh"
|
|
#include "common/gui_window.hh"
|
|
#include "common/life.hh"
|
|
#include "common/logger_ref.hh"
|
|
#include "common/nfile_watcher.hh"
|
|
#include "common/node.hh"
|
|
#include "common/ptr_selector.hh"
|
|
#include "common/yas_enum.hh"
|
|
|
|
|
|
using namespace std::literals;
|
|
|
|
namespace nf7 {
|
|
namespace {
|
|
|
|
struct CreateParam {
|
|
nf7::File* file;
|
|
std::shared_ptr<nf7::LoggerRef> log;
|
|
|
|
std::shared_ptr<nf7::Context> ctx;
|
|
std::shared_ptr<nf7::NFileWatcher> nwatch;
|
|
std::shared_ptr<nf7::Env::Watcher> watch;
|
|
};
|
|
template <typename T>
|
|
struct HandleParam {
|
|
nf7::File* file;
|
|
std::shared_ptr<nf7::LoggerRef> log;
|
|
|
|
std::shared_ptr<nf7::Node::Lambda> la;
|
|
nf7::Node::Lambda::Msg in;
|
|
nf7::Mutex::Resource<std::shared_ptr<T>> obj;
|
|
};
|
|
|
|
template <typename T>
|
|
concept HasWindow = requires(T& x) {
|
|
x.UpdateWindow(std::optional<nf7::Future<std::shared_ptr<typename T::Product>>> {});
|
|
};
|
|
|
|
template <typename T>
|
|
class ObjBase : public nf7::FileBase,
|
|
public nf7::GenericConfig, 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 = typename T::Product;
|
|
using Resource = nf7::Mutex::Resource<std::shared_ptr<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),
|
|
nf7::GenericConfig(mem_),
|
|
nf7::DirItem(nf7::DirItem::kMenu |
|
|
nf7::DirItem::kTooltip),
|
|
nf7::Node(nf7::Node::kNone),
|
|
life_(*this),
|
|
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
|
nwatch_(std::make_shared<nf7::NFileWatcher>(*this)),
|
|
mem_(*this, std::move(data)) {
|
|
nwatch_->onMod = mem_.onRestore = mem_.onCommit = [this]() {
|
|
Drop(true /* = quiet */);
|
|
Touch();
|
|
};
|
|
|
|
if constexpr (HasWindow<T>) {
|
|
win_.emplace(*this, T::kWindowTitle);
|
|
win_->onConfig = []() {
|
|
const auto em = ImGui::GetFontSize();
|
|
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
|
|
};
|
|
win_->onUpdate = [this]() { mem_->UpdateWindow(fu_); };
|
|
}
|
|
}
|
|
|
|
ObjBase(nf7::Deserializer& ar) : ObjBase(ar.env()) {
|
|
ar(mem_.data());
|
|
if constexpr (HasWindow<T>) {
|
|
ar(*win_);
|
|
}
|
|
}
|
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
|
ar(mem_.data());
|
|
if constexpr (HasWindow<T>) {
|
|
ar(*win_);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
nf7::Node::Meta GetMeta() const noexcept override {
|
|
return {T::kInputs, T::kOutputs};
|
|
}
|
|
|
|
ResourceFuture Create() noexcept final {
|
|
return Create(true);
|
|
}
|
|
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_) {
|
|
watch_ = std::make_shared<nf7::GenericWatcher>(env());
|
|
watch_->AddHandler(nf7::File::Event::kUpdate, [self = life_.ref()](auto&) {
|
|
if (self) self->Drop();
|
|
});
|
|
nwatch_->Clear();
|
|
|
|
fu_ = mem_->Create(CreateParam {
|
|
.file = this,
|
|
.log = log_,
|
|
.ctx = ctx,
|
|
.nwatch = nwatch_,
|
|
.watch = watch_,
|
|
});
|
|
}
|
|
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 (HasWindow<T>) {
|
|
ImGui::Separator();
|
|
win_->MenuItem();
|
|
}
|
|
}
|
|
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::Config, nf7::DirItem, nf7::Memento, nf7::Node, ThisFactory>(t).Select(this, &mem_);
|
|
}
|
|
|
|
private:
|
|
nf7::Life<ThisObjBase> life_;
|
|
std::shared_ptr<nf7::LoggerRef> log_;
|
|
|
|
std::shared_ptr<nf7::GenericWatcher> watch_;
|
|
std::shared_ptr<nf7::NFileWatcher> nwatch_;
|
|
|
|
nf7::Mutex mtx_;
|
|
|
|
std::optional<nf7::Future<std::shared_ptr<Product>>> fu_;
|
|
|
|
nf7::GenericMemento<T> mem_;
|
|
std::optional<nf7::gui::Window> win_;
|
|
|
|
|
|
void Drop(bool quiet = false) noexcept {
|
|
auto ctx = std::make_shared<nf7::GenericContext>(*this, "dropping OpenGL obj");
|
|
mtx_.AcquireLock(ctx, true /* = exclusive */).
|
|
ThenIf([this, quiet](auto&) {
|
|
fu_ = std::nullopt;
|
|
if (!quiet) 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 {
|
|
f_.EnforceAlive();
|
|
const auto mod = f_->mem_->Handle(HandleParam<Product> {
|
|
.file = &*f_,
|
|
.log = f_->log_,
|
|
.la = shared_from_this(),
|
|
.in = in,
|
|
.obj = obj,
|
|
});
|
|
if (mod) 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 CreateParam& p) noexcept {
|
|
const Product::Meta meta {
|
|
.target = target_,
|
|
};
|
|
return meta.Create(p.ctx);
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>& p) {
|
|
if (p.in.name == "upload") {
|
|
const auto& vec = p.in.value.vector();
|
|
const auto usage = gl::ToEnum(usage_);
|
|
|
|
if (vec->size() == 0) return false;
|
|
|
|
p.la->env().ExecGL(p.la, [=]() {
|
|
const auto n = static_cast<GLsizeiptr>(vec->size());
|
|
|
|
auto& buf = **p.obj;
|
|
const auto t = gl::ToEnum(buf.meta().target);
|
|
glBindBuffer(t, buf.id());
|
|
{
|
|
auto& size = buf.param().size;
|
|
if (size != vec->size()) {
|
|
size = vec->size();
|
|
glBufferData(t, n, vec->data(), usage);
|
|
} else {
|
|
glBufferSubData(t, 0, n, vec->data());
|
|
}
|
|
}
|
|
glBindBuffer(t, 0);
|
|
assert(0 == glGetError());
|
|
});
|
|
return true;
|
|
} else {
|
|
throw nf7::Exception {"unknown input: "+p.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->param().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_, ifmt_, 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 << "ifmt";
|
|
st << YAML::Value << std::string {magic_enum::enum_name(ifmt_)};
|
|
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 ifmt = magic_enum::
|
|
enum_cast<gl::InternalFormat>(yaml["ifmt"].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;
|
|
ifmt_ = ifmt;
|
|
|
|
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 CreateParam& p) noexcept
|
|
try {
|
|
Product::Meta meta {
|
|
.target = target_,
|
|
.format = ifmt_,
|
|
.size = {},
|
|
};
|
|
std::transform(size_.begin(), size_.end(), meta.size.begin(),
|
|
[](auto x) { return static_cast<GLsizei>(x); });
|
|
return meta.Create(p.ctx);
|
|
} catch (nf7::Exception&) {
|
|
return {std::current_exception()};
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>& p) {
|
|
if (p.in.name == "upload") {
|
|
const auto& v = p.in.value;
|
|
|
|
const auto vec = v.tuple("vec").vector();
|
|
auto& tex = **p.obj;
|
|
|
|
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}).integerOrScalar<uint32_t>();
|
|
size[i] = v.tuple(kSizeNames[i]).integerOrScalar<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::GetByteSize(ifmt_);
|
|
if (vec->size() < static_cast<size_t>(vecsz)) {
|
|
throw nf7::Exception {"vector is too small"};
|
|
}
|
|
|
|
const auto fmt = gl::ToEnum(gl::GetColorComp(ifmt_));
|
|
const auto type = gl::ToEnum(gl::GetNumericType(ifmt_));
|
|
p.la->env().ExecGL(p.la, [=, &tex]() {
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
const auto t = gl::ToEnum(tex.meta().target);
|
|
glBindTexture(t, tex.id());
|
|
switch (t) {
|
|
case GL_TEXTURE_2D:
|
|
case GL_TEXTURE_RECTANGLE:
|
|
glTexSubImage2D(t, 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(t, 0);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
assert(0 == glGetError());
|
|
});
|
|
return true;
|
|
|
|
} else if (p.in.name == "download") {
|
|
auto numtype = gl::GetNumericType(ifmt_);
|
|
auto comp = gl::GetColorComp(ifmt_);
|
|
try {
|
|
try {
|
|
numtype = magic_enum::enum_cast<
|
|
gl::NumericType>(p.in.value.tuple("numtype").string()).value();
|
|
} catch (nf7::Exception&) {
|
|
}
|
|
try {
|
|
comp = magic_enum::enum_cast<
|
|
gl::ColorComp>(p.in.value.tuple("comp").string()).value();
|
|
} catch (nf7::Exception&) {
|
|
}
|
|
} catch (std::bad_optional_access&) {
|
|
throw nf7::Exception {"unknown enum"};
|
|
}
|
|
|
|
p.la->env().ExecGL(p.la, [=]() {
|
|
GLuint pbo;
|
|
glGenBuffers(1, &pbo);
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
|
|
|
const auto& tex = **p.obj;
|
|
const auto size = tex.meta().size;
|
|
const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {});
|
|
const auto bsize = texel*gl::GetCompCount(comp)*gl::GetByteSize(numtype);
|
|
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(bsize), nullptr, GL_STREAM_READ);
|
|
|
|
const auto t = gl::ToEnum(tex.meta().target);
|
|
glBindTexture(t, tex.id());
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
glGetTexImage(t, 0, gl::ToEnum(comp), gl::ToEnum(numtype), nullptr);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
glBindTexture(t, 0);
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
assert(0 == glGetError());
|
|
|
|
nf7::gl::ExecFenceSync(p.la).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, static_cast<size_t>(bsize));
|
|
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
glDeleteBuffers(1, &pbo);
|
|
assert(0 == glGetError());
|
|
|
|
p.la->env().ExecSub(p.la, [=, &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},
|
|
}};
|
|
p.in.sender->Handle("buffer", std::move(v), p.la);
|
|
});
|
|
});
|
|
});
|
|
return false;
|
|
} else {
|
|
throw nf7::Exception {"unknown input: "+p.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 c = magic_enum::enum_name(ifmt_);
|
|
ImGui::Text("ifmt : %.*s",
|
|
static_cast<int>(c.size()), c.data());
|
|
|
|
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.target == gl::TextureTarget::Tex2D) {
|
|
ImGui::Spacing();
|
|
ImGui::TextUnformatted("preview:");
|
|
ImGui::Image(reinterpret_cast<void*>(id),
|
|
ImVec2 {static_cast<float>(size_[0]), static_cast<float>(size_[1])});
|
|
}
|
|
}
|
|
}
|
|
|
|
static constexpr const char* kWindowTitle = "Texture Viewer";
|
|
void UpdateWindow(const std::optional<nf7::Future<std::shared_ptr<Product>>>& fu) noexcept {
|
|
if (!fu) {
|
|
ImGui::TextUnformatted("this object is not used yet");
|
|
return;
|
|
}
|
|
if (fu->error()) {
|
|
ImGui::TextUnformatted("error while texture creation ;(");
|
|
return;
|
|
}
|
|
if (fu->yet()) {
|
|
ImGui::TextUnformatted("creating new texture... X)");
|
|
return;
|
|
}
|
|
assert(fu->done());
|
|
|
|
const auto& tex = *fu->value();
|
|
if (tex.meta().target != gl::TextureTarget::Tex2D) {
|
|
ImGui::TextUnformatted("only Tex2D texture is supported");
|
|
return;
|
|
}
|
|
|
|
const auto avail = ImGui::GetContentRegionAvail();
|
|
const auto aspect =
|
|
static_cast<float>(tex.meta().size[0]) /
|
|
static_cast<float>(tex.meta().size[1]);
|
|
|
|
auto size = ImVec2 {avail.x, avail.x/aspect};
|
|
if (size.y > avail.y) {
|
|
size = ImVec2 {avail.y*aspect, avail.y};
|
|
}
|
|
|
|
const auto id = reinterpret_cast<ImTextureID>(static_cast<intptr_t>(tex.id()));
|
|
ImGui::SetCursorPos(ImGui::GetCursorPos()+(avail-size)/2);
|
|
ImGui::Image(id, size);
|
|
}
|
|
|
|
private:
|
|
gl::TextureTarget target_ = gl::TextureTarget::Rect;
|
|
gl::InternalFormat ifmt_ = gl::InternalFormat::RGBA8;
|
|
|
|
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 CreateParam& p) noexcept {
|
|
nf7::Future<std::shared_ptr<Product>>::Promise pro {p.ctx};
|
|
|
|
auto ost = std::make_shared<std::ostringstream>();
|
|
auto ist = std::make_shared<std::istringstream>(src_);
|
|
auto path = p.ctx->env().npath() / "INLINE_TEXT";
|
|
|
|
auto preproc = std::make_shared<gl::ShaderPreproc>(p.ctx, ost, ist, std::move(path));
|
|
preproc->ExecProcess();
|
|
preproc->future().Chain(p.ctx, pro, [=, type = type_](auto&) mutable {
|
|
const Product::Meta meta {
|
|
.type = type,
|
|
};
|
|
meta.Create(p.ctx, ost->str()).Chain(pro);
|
|
});
|
|
return pro.future().
|
|
ThenIf(p.ctx, [=](auto&) mutable {
|
|
for (const auto& npath : preproc->nfiles()) {
|
|
p.nwatch->Watch(npath);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>&) {
|
|
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_, depth_);
|
|
}
|
|
|
|
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;
|
|
|
|
if (depth_) {
|
|
st << YAML::Key << "depth";
|
|
st << YAML::BeginMap;
|
|
st << YAML::Key << "near";
|
|
st << YAML::Value << depth_->near;
|
|
st << YAML::Key << "far";
|
|
st << YAML::Value << depth_->far;
|
|
st << YAML::Key << "func";
|
|
st << YAML::Value << std::string {magic_enum::enum_name(depth_->func)};
|
|
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);
|
|
|
|
Program ret;
|
|
for (const auto& shader : yaml["shaders"]) {
|
|
ret.shaders_.push_back(
|
|
nf7::File::Path::Parse(shader.as<std::string>()));
|
|
}
|
|
if (ret.shaders_.size() == 0) {
|
|
throw nf7::Exception {"no shader is attached"};
|
|
}
|
|
|
|
if (const auto& yaml_depth = yaml["depth"]) {
|
|
depth_.emplace(Product::Meta::Depth {
|
|
.near = yaml_depth["near"].as<float>(),
|
|
.far = yaml_depth["far"].as<float>(),
|
|
.func = magic_enum::enum_cast<gl::TestFunc>(
|
|
yaml_depth["func"].as<std::string>()).value(),
|
|
});
|
|
}
|
|
|
|
*this = std::move(ret);
|
|
} catch (YAML::Exception& e) {
|
|
throw nf7::Exception {std::string {"YAML error: "}+e.what()};
|
|
}
|
|
|
|
nf7::Future<std::shared_ptr<Product>> Create(const CreateParam& p) noexcept
|
|
try {
|
|
auto& base = *p.file;
|
|
|
|
std::vector<nf7::File::Id> shaders;
|
|
for (const auto& path : shaders_) {
|
|
const auto fid = base.ResolveOrThrow(path).id();
|
|
p.watch->Watch(fid);
|
|
shaders.push_back(fid);
|
|
}
|
|
|
|
Product::Meta meta;
|
|
if (depth_) {
|
|
meta.depth.emplace(*depth_);
|
|
}
|
|
|
|
return meta.Create(p.ctx, shaders);
|
|
} catch (nf7::Exception&) {
|
|
return {std::current_exception()};
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>& p) {
|
|
const auto& base = *p.file;
|
|
const auto& v = p.in.value;
|
|
|
|
if (p.in.name == "draw") {
|
|
const auto mode = gl::ToEnum<gl::DrawMode>(v.tuple("mode").string());
|
|
const auto count = v.tuple("count").integerOrScalar<GLsizei>();
|
|
const auto inst = v.tupleOr("instance", nf7::Value::Integer{1}).integerOrScalar<GLsizei>();
|
|
|
|
const auto uni = v.tupleOr("uniform", nf7::Value::Tuple {}).tuple();
|
|
const auto tex = v.tupleOr("texture", nf7::Value::Tuple {}).tuple();
|
|
if (tex->size() > GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) {
|
|
throw nf7::Exception {"too many textures specified"};
|
|
}
|
|
|
|
const auto& vp = v.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"};
|
|
}
|
|
|
|
gl::Program::Meta config = (**p.obj).meta();
|
|
// TODO: override configurations
|
|
|
|
// this will be triggered when all preparation done
|
|
nf7::AggregatePromise apro {p.la};
|
|
|
|
// find, fetch and lock FBO
|
|
auto fbo_fu = gl::LockRecursively<gl::Framebuffer>(
|
|
v.tuple("fbo").
|
|
file(base).
|
|
interfaceOrThrow<nf7::gl::Framebuffer::Factory>(),
|
|
p.la);
|
|
apro.Add(fbo_fu);
|
|
|
|
// find, fetch and lock VAO
|
|
auto vao_fu = gl::LockRecursively<gl::VertexArray>(
|
|
v.tuple("vao").
|
|
file(base).
|
|
interfaceOrThrow<nf7::gl::VertexArray::Factory>(),
|
|
p.la,
|
|
gl::VertexArray::Meta::ValidationHint {
|
|
.vertices = static_cast<size_t>(count),
|
|
.instances = static_cast<size_t>(inst),
|
|
});
|
|
apro.Add(vao_fu);
|
|
|
|
// find, fetch and lock textures
|
|
std::vector<std::pair<std::string, gl::Texture::Factory::Product>> tex_fu;
|
|
tex_fu.reserve(tex->size());
|
|
for (auto& pa : *tex) {
|
|
auto fu = base.
|
|
ResolveOrThrow(pa.second.string()).
|
|
interfaceOrThrow<gl::Texture::Factory>().
|
|
Create();
|
|
tex_fu.emplace_back(pa.first, fu);
|
|
apro.Add(fu);
|
|
}
|
|
|
|
// execute drawing after successful locking
|
|
apro.future().ThenIf(nf7::Env::kGL, p.la, [=, tex_fu = std::move(tex_fu)](auto&) {
|
|
const auto& fbo = *fbo_fu.value().first;
|
|
const auto& vao = *vao_fu.value().first;
|
|
const auto& prog = *p.obj;
|
|
|
|
// 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& pa : *uni) {
|
|
try {
|
|
SetUniform(prog->id(), pa.first.c_str(), pa.second);
|
|
} catch (nf7::Exception&) {
|
|
p.log->Warn("uniform '"+pa.first+"' is ignored");
|
|
}
|
|
}
|
|
|
|
// bind textures
|
|
for (size_t i = 0; i < tex_fu.size(); ++i) {
|
|
const auto& pa = tex_fu[i];
|
|
try {
|
|
const GLint loc = glGetUniformLocation(prog->id(), pa.first.c_str());
|
|
if (loc < 0) {
|
|
throw nf7::Exception {"missing uniform to bind texture"};
|
|
}
|
|
const auto& tex = *pa.second.value();
|
|
glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + i));
|
|
glBindTexture(gl::ToEnum(tex->meta().target), tex->id());
|
|
glUniform1i(loc, static_cast<GLint>(i));
|
|
} catch (nf7::Exception&) {
|
|
p.log->Warn("texture '"+pa.first+"' is ignored");
|
|
}
|
|
}
|
|
|
|
// draw
|
|
config.ApplyState();
|
|
if (vao->meta().index) {
|
|
const auto numtype = gl::ToEnum(vao->meta().index->numtype);
|
|
glDrawElementsInstanced(mode, count, numtype, nullptr, inst);
|
|
} else {
|
|
glDrawArraysInstanced(mode, 0, count, inst);
|
|
}
|
|
config.RevertState();
|
|
const auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
// unbind all
|
|
glBindVertexArray(0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glUseProgram(0);
|
|
|
|
if (0 != glGetError()) {
|
|
p.log->Warn("OpenGL error detected, drawing might be incomplete");
|
|
}
|
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
p.log->Warn("framebuffer is broken");
|
|
}
|
|
}).Catch<nf7::Exception>([p](auto& e) {
|
|
p.log->Error(e);
|
|
});
|
|
return false;
|
|
} else {
|
|
throw nf7::Exception {"unknown input: "+p.in.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_;
|
|
std::optional<Product::Meta::Depth> depth_ = {{}};
|
|
|
|
|
|
static void SetUniform(GLuint prog, const char* name, const nf7::Value& v) {
|
|
const GLint loc = glGetUniformLocation(prog, name);
|
|
if (loc < 0) {
|
|
throw nf7::Exception {"unknown uniform identifier"};
|
|
}
|
|
|
|
// single integer
|
|
try {
|
|
glUniform1i(loc, v.integer<GLint>());
|
|
return;
|
|
} catch (nf7::Exception&) {
|
|
}
|
|
|
|
// single float
|
|
try {
|
|
glUniform1f(loc, v.scalar<GLfloat>());
|
|
return;
|
|
} 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;
|
|
} catch (nf7::Exception&) {
|
|
}
|
|
|
|
throw nf7::Exception {"the value is not compatible with any uniform type"};
|
|
}
|
|
};
|
|
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 location = 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(location, size, type, normalize, stride, offset, divisor, buffer);
|
|
}
|
|
|
|
const char* Validate() const noexcept {
|
|
if (location >= GL_MAX_VERTEX_ATTRIBS) {
|
|
return "too huge location";
|
|
}
|
|
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.location);
|
|
(void) itr;
|
|
if (!uniq) {
|
|
throw nf7::Exception {"attribute location duplicated"};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
VertexArray() = default;
|
|
VertexArray(const VertexArray&) = default;
|
|
VertexArray(VertexArray&&) = default;
|
|
VertexArray& operator=(const VertexArray&) = default;
|
|
VertexArray& operator=(VertexArray&&) = default;
|
|
|
|
void serialize(auto& ar) {
|
|
ar(index_, index_numtype_, attrs_);
|
|
Attr::Validate(attrs_);
|
|
}
|
|
|
|
std::string Stringify() noexcept {
|
|
YAML::Emitter st;
|
|
st << YAML::BeginMap;
|
|
st << YAML::Key << "index";
|
|
st << YAML::BeginMap;
|
|
st << YAML::Key << "buffer";
|
|
st << YAML::Value << index_.Stringify();
|
|
st << YAML::Key << "type";
|
|
st << YAML::Value << std::string {magic_enum::enum_name(index_numtype_)};
|
|
st << YAML::EndMap;
|
|
st << YAML::Key << "attrs";
|
|
st << YAML::Value << YAML::BeginSeq;
|
|
for (const auto& attr : attrs_) {
|
|
st << YAML::BeginMap;
|
|
st << YAML::Key << "location";
|
|
st << YAML::Value << attr.location;
|
|
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);
|
|
|
|
auto index = nf7::File::Path::Parse(yaml["index"]["buffer"].as<std::string>());
|
|
|
|
const auto index_numtype = magic_enum::enum_cast<gl::NumericType>(
|
|
yaml["index"]["type"].as<std::string>()).value();
|
|
|
|
std::vector<Attr> attrs;
|
|
for (const auto& attr : yaml["attrs"]) {
|
|
attrs.push_back({
|
|
.location = attr["location"].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);
|
|
|
|
index_ = std::move(index);
|
|
index_numtype_ = index_numtype;
|
|
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 CreateParam& p) noexcept
|
|
try {
|
|
auto& base = *p.file;
|
|
Product::Meta meta;
|
|
|
|
if (index_.terms().size() > 0) {
|
|
const auto fid = base.ResolveOrThrow(index_).id();
|
|
p.watch->Watch(fid);
|
|
|
|
meta.index.emplace();
|
|
meta.index->buffer = fid;
|
|
meta.index->numtype = index_numtype_;
|
|
}
|
|
|
|
meta.attrs.reserve(attrs_.size());
|
|
for (auto& attr : attrs_) {
|
|
const auto fid = base.ResolveOrThrow(attr.buffer).id();
|
|
p.watch->Watch(fid);
|
|
meta.attrs.push_back({
|
|
.buffer = fid,
|
|
.location = attr.location,
|
|
.size = attr.size,
|
|
.type = attr.type,
|
|
.normalize = attr.normalize,
|
|
.stride = attr.stride,
|
|
.offset = attr.offset,
|
|
.divisor = attr.divisor,
|
|
});
|
|
}
|
|
return meta.Create(p.ctx);
|
|
} catch (nf7::Exception&) {
|
|
return {std::current_exception()};
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>&) {
|
|
return false;
|
|
}
|
|
|
|
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
|
|
if (prod) {
|
|
ImGui::Text("id: %zu", static_cast<size_t>(prod->id()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
nf7::File::Path index_;
|
|
gl::NumericType index_numtype_;
|
|
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", "blit",
|
|
};
|
|
static inline const std::vector<std::string> kOutputs = {
|
|
};
|
|
|
|
using Product = nf7::gl::Framebuffer;
|
|
|
|
struct Attachment {
|
|
nf7::File::Path path;
|
|
|
|
void serialize(auto& ar) {
|
|
ar(path);
|
|
}
|
|
};
|
|
|
|
Framebuffer() = default;
|
|
Framebuffer(const Framebuffer&) = default;
|
|
Framebuffer(Framebuffer&&) = default;
|
|
Framebuffer& operator=(const Framebuffer&) = default;
|
|
Framebuffer& operator=(Framebuffer&&) = default;
|
|
|
|
void serialize(auto& ar) {
|
|
ar(colors_, depth_, stencil_);
|
|
}
|
|
|
|
std::string Stringify() noexcept {
|
|
YAML::Emitter st;
|
|
st << YAML::BeginMap;
|
|
st << YAML::Key << "colors";
|
|
st << YAML::Value << YAML::BeginMap;
|
|
for (size_t i = 0; i < Product::Meta::kColorSlotCount; ++i) {
|
|
if (colors_[i]) {
|
|
st << YAML::Key << i;
|
|
st << YAML::Value << colors_[i]->path.Stringify();
|
|
}
|
|
}
|
|
st << YAML::EndMap;
|
|
if (depth_) {
|
|
st << YAML::Key << "depth";
|
|
st << YAML::Value << depth_->path.Stringify();
|
|
}
|
|
if (stencil_) {
|
|
st << YAML::Key << "stencil";
|
|
st << YAML::Value << stencil_->path.Stringify();
|
|
}
|
|
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_colors = yaml["colors"];
|
|
|
|
Framebuffer ret;
|
|
for (size_t i = 0; i < Product::Meta::kColorSlotCount; ++i) {
|
|
if (auto& yaml_color = yaml_colors[std::to_string(i)]) {
|
|
ret.colors_[i].emplace(Attachment {
|
|
.path = nf7::File::Path::Parse(yaml_color.as<std::string>()),
|
|
});
|
|
}
|
|
}
|
|
if (const auto& yaml_depth = yaml["depth"]) {
|
|
ret.depth_.emplace(Attachment {
|
|
.path = nf7::File::Path::Parse(yaml_depth.as<std::string>()),
|
|
});
|
|
}
|
|
if (const auto& yaml_stencil = yaml["stencil"]) {
|
|
ret.stencil_.emplace(Attachment {
|
|
.path = nf7::File::Path::Parse(yaml_stencil.as<std::string>()),
|
|
});
|
|
}
|
|
|
|
*this = std::move(ret);
|
|
} 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 CreateParam& p) noexcept
|
|
try {
|
|
auto& base = *p.file;
|
|
Product::Meta meta;
|
|
|
|
const auto resolveAndWatch = [&](const auto& path) {
|
|
const auto fid = base.ResolveOrThrow(path).id();
|
|
p.watch->Watch(fid);
|
|
return fid;
|
|
};
|
|
|
|
for (size_t i = 0; i < Product::Meta::kColorSlotCount; ++i) {
|
|
if (const auto& color = colors_[i]) {
|
|
meta.colors[i].emplace(Product::Meta::Attachment {
|
|
.tex = resolveAndWatch(color->path),
|
|
});
|
|
}
|
|
}
|
|
if (depth_) {
|
|
meta.depth.emplace(Product::Meta::Attachment {
|
|
.tex = resolveAndWatch(depth_->path),
|
|
});
|
|
}
|
|
if (stencil_) {
|
|
meta.stencil.emplace(Product::Meta::Attachment {
|
|
.tex = resolveAndWatch(stencil_->path),
|
|
});
|
|
}
|
|
return meta.Create(p.ctx);
|
|
} catch (nf7::Exception&) {
|
|
return {std::current_exception()};
|
|
}
|
|
|
|
bool Handle(const HandleParam<Product>& p) {
|
|
if (p.in.name == "clear") {
|
|
(**p.obj).meta().LockAttachments(p.la).ThenIf(nf7::Env::kGL, p.la, [=](auto&) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, (**p.obj).id());
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
});
|
|
return true;
|
|
} else if (p.in.name == "blit") {
|
|
std::array<GLint, 8> rect;
|
|
const auto& rect_tup = p.in.value.tuple("rect");
|
|
for (size_t i = 0; i < rect.size(); ++i) {
|
|
rect[i] = rect_tup.tuple(i).integerOrScalar<GLint>();
|
|
}
|
|
|
|
nf7::AggregatePromise apro {p.la};
|
|
|
|
auto dst_lock_fu = (**p.obj).meta().LockAttachments(p.la);
|
|
apro.Add(dst_lock_fu);
|
|
|
|
auto src_fu = gl::LockRecursively<gl::Framebuffer>(
|
|
p.in.value.tuple("src").
|
|
file(*p.file).interfaceOrThrow<gl::Framebuffer::Factory>(),
|
|
p.la);
|
|
apro.Add(src_fu);
|
|
|
|
apro.future().ThenIf(nf7::Env::kGL, p.la, [=](auto&) {
|
|
const auto& src = **src_fu.value().first;
|
|
const auto& dst = **p.obj;
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, src.id());
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst.id());
|
|
|
|
glBlitFramebuffer(rect[0], rect[1], rect[2], rect[3],
|
|
rect[4], rect[5], rect[6], rect[7],
|
|
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
if (0 != glGetError()) {
|
|
p.log->Warn("failed to blit framebuffer (maybe incompatible buffer format)");
|
|
}
|
|
});
|
|
return true;
|
|
} else {
|
|
throw nf7::Exception {"unknown command: "+p.in.name};
|
|
}
|
|
}
|
|
|
|
void UpdateTooltip(const std::shared_ptr<Product>& prod) noexcept {
|
|
if (prod) {
|
|
ImGui::Text("id: %zu", static_cast<size_t>(prod->id()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::array<std::optional<Attachment>, Product::Meta::kColorSlotCount> colors_;
|
|
std::optional<Attachment> depth_;
|
|
std::optional<Attachment> stencil_;
|
|
};
|
|
template <>
|
|
struct ObjBase<Framebuffer>::TypeInfo final {
|
|
static inline const nf7::GenericTypeInfo<ObjBase<Framebuffer>> kType = {"GL/Framebuffer", {"nf7::DirItem"}};
|
|
};
|
|
|
|
}
|
|
} // namespace nf7
|