diff --git a/common/gl_obj.cc b/common/gl_obj.cc index a40f5da..2fa05c4 100644 --- a/common/gl_obj.cc +++ b/common/gl_obj.cc @@ -240,4 +240,95 @@ try { return { std::current_exception() }; } + +nf7::Future>> Obj_FramebufferMeta::Create( + const std::shared_ptr& ctx, + std::vector&& atts) noexcept { + nf7::Future>>::Promise pro {ctx}; + LockAttachments(ctx, atts). + Chain(nf7::Env::kGL, ctx, pro, [ctx, atts = std::move(atts)](auto& texs) mutable { + assert(atts.size() == texs.size()); + + GLuint id; + glGenFramebuffers(1, &id); + + const char* err = nullptr; + glBindFramebuffer(GL_FRAMEBUFFER, id); + for (size_t i = 0; i < atts.size() && !err; ++i) { + glFramebufferTexture(GL_FRAMEBUFFER, atts[i].slot, (*texs[i])->id(), 0); + if (0 != glGetError()) { + err = "failed to attach texture"; + } + } + const auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + assert(0 == glGetError()); + + const auto ret = std::make_shared>(ctx, id, std::move(atts)); + if (err) { + throw nf7::Exception {err}; + } + ThrowStatus(status); + return ret; + }); + return pro.future(); +} + +nf7::Future>>> + Obj_FramebufferMeta::LockAttachments( + const std::shared_ptr& ctx, + std::span attachments) noexcept +try { + nf7::AggregatePromise apro {ctx}; + std::vector>>> fus; + + for (const auto& attachment : attachments) { + nf7::Future>>::Promise pro {ctx}; + auto fu = ctx->env().GetFileOrThrow(attachment.tex). + interfaceOrThrow().Create(). + Chain(nf7::Env::kGL, ctx, pro, [](auto& tex) { + if ((*tex)->meta().type != GL_TEXTURE_2D) { + throw nf7::Exception {"only 2D texture is allowed"}; + } + return tex; + }); + + fus.push_back(fu); + apro.Add(fu); + } + + nf7::Future>>>::Promise pro {ctx}; + apro.future().Chain(pro, [fus = std::move(fus)](auto&) { + std::vector>> ret; + for (auto& fu : fus) { + ret.emplace_back(fu.value()); + } + return ret; + }); + return pro.future(); +} catch (nf7::Exception&) { + return { std::current_exception() }; +} + +void Obj_FramebufferMeta::ThrowStatus(GLenum status) { + switch (status) { + case GL_FRAMEBUFFER_COMPLETE: + return; + case GL_FRAMEBUFFER_UNDEFINED: + throw nf7::Exception {"no framebuffer bound"}; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw nf7::Exception {"no framebuffer bound"}; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw nf7::Exception {"nothing attached"}; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + throw nf7::Exception {"no color attachments"}; + case GL_FRAMEBUFFER_UNSUPPORTED: + throw nf7::Exception {"unsupported internal format"}; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + throw nf7::Exception {"incomplete multisample"}; + default: + throw nf7::Exception {"unknown framebuffer status"}; + } +} + } // namespace nf7::gl diff --git a/common/gl_obj.hh b/common/gl_obj.hh index e22d593..6d1623b 100644 --- a/common/gl_obj.hh +++ b/common/gl_obj.hh @@ -21,7 +21,7 @@ class Obj final { public: using Meta = T; - // NOT thread-safe + // must be called from main or sub task template static nf7::Future>> Create(Args&&... args) noexcept { return Meta::Create(std::forward(args)...); @@ -54,6 +54,7 @@ class Obj final { struct Obj_BufferMeta final { public: + // must be called from main or sub task static nf7::Future>> Create( const std::shared_ptr& ctx, GLenum type) noexcept; @@ -99,6 +100,7 @@ using TextureFactory = AsyncFactory>> Create( const std::shared_ptr& ctx, GLenum type, @@ -120,6 +122,7 @@ using ShaderFactory = AsyncFactory> struct Obj_ProgramMeta final { public: + // must be called from main or sub task static nf7::Future>> Create( const std::shared_ptr& ctx, const std::vector& shaders) noexcept; @@ -147,6 +150,7 @@ struct Obj_VertexArrayMeta final { GLuint divisor; }; + // must be called from main or sub task static nf7::Future>> Create( const std::shared_ptr& ctx, std::vector&& attrs) noexcept; @@ -154,6 +158,7 @@ struct Obj_VertexArrayMeta final { glDeleteVertexArrays(1, &id); } + // must be called from main or sub task static nf7::Future>>> LockBuffers( const std::shared_ptr& ctx, @@ -173,4 +178,42 @@ struct Obj_VertexArrayMeta final { using VertexArray = Obj; using VertexArrayFactory = AsyncFactory>>; + +struct Obj_FramebufferMeta final { + public: + struct Attachment { + nf7::File::Id tex; + GLenum slot; + }; + + // must be called from main or sub task + static nf7::Future>> Create( + const std::shared_ptr& ctx, + std::vector&& attachments) noexcept; + + static void Delete(GLuint id) noexcept { + glDeleteFramebuffers(1, &id); + } + + // must be called from main or sub task + static nf7::Future>>> LockAttachments( + const std::shared_ptr& ctx, + std::span attachments) noexcept; + + // must be called on GL thread + static void ThrowStatus(GLenum status); + + Obj_FramebufferMeta(std::vector&& a) noexcept : attachments(std::move(a)) { + } + + nf7::Future>>> LockAttachments( + const std::shared_ptr& ctx) noexcept { + return LockAttachments(ctx, attachments); + } + + const std::vector attachments; +}; +using Framebuffer = Obj; +using FramebufferFactory = AsyncFactory>>; + } // namespace nf7::gl diff --git a/file/gl_obj.cc b/file/gl_obj.cc index 0151eb4..c4848a6 100644 --- a/file/gl_obj.cc +++ b/file/gl_obj.cc @@ -815,7 +815,7 @@ struct VertexArray { } return nullptr; } - static void Validate(const std::vector& attrs) { + static void Validate(std::span attrs) { for (auto& attr : attrs) { if (const auto msg = attr.Validate()) { throw nf7::Exception {"invalid attribute: "s+msg}; @@ -955,6 +955,129 @@ struct ObjBase::TypeInfo final { static inline const nf7::GenericTypeInfo> kType = {"GL/VertexArray", {"nf7::DirItem"}}; }; + +struct Framebuffer { + public: + static void UpdateTypeTooltip() noexcept { + ImGui::TextUnformatted("OpenGL Framebuffer Object"); + } + + static inline const std::vector kInputs = { + }; + static inline const std::vector kOutputs = { + }; + + using Product = nf7::gl::Framebuffer; + + enum class Slot { + Color0, Color1, Color2, Color3, Color4, Color5, Color6, Color7, + }; + struct Attachment { + public: + Slot 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 attachments; + for (auto [slot, name] : magic_enum::enum_entries()) { + if (const auto& yaml_attachment = yaml_attachments[std::string {name}]) { + attachments.push_back({ + .slot = slot, + .path = nf7::File::Path::Parse(yaml_attachment.as()), + }); + } + } + 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> Create(const std::shared_ptr& ctx) noexcept + try { + auto& base = ctx->env().GetFileOrThrow(ctx->initiator()); + + std::vector 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 = ToSlot(attachment.slot), + }); + } + return Product::Create(ctx, std::move(attachments)); + } catch (nf7::Exception&) { + return {std::current_exception()}; + } + + bool Handle(const std::shared_ptr&, + const nf7::Mutex::Resource>&, + const nf7::Node::Lambda::Msg&) { + return false; + } + + void UpdateTooltip(const std::shared_ptr& prod) noexcept { + if (prod) { + ImGui::Text("id: %zu", static_cast(prod->id())); + } + } + + private: + std::vector attachments_; + + static GLenum ToSlot(Slot slot) { + return + slot == Slot::Color0? GL_COLOR_ATTACHMENT0: + slot == Slot::Color1? GL_COLOR_ATTACHMENT0+1: + slot == Slot::Color2? GL_COLOR_ATTACHMENT0+2: + slot == Slot::Color3? GL_COLOR_ATTACHMENT0+3: + slot == Slot::Color4? GL_COLOR_ATTACHMENT0+4: + slot == Slot::Color5? GL_COLOR_ATTACHMENT0+5: + slot == Slot::Color6? GL_COLOR_ATTACHMENT0+6: + slot == Slot::Color7? GL_COLOR_ATTACHMENT0+7: + throw 0; + } +}; +template <> +struct ObjBase::TypeInfo final { + static inline const nf7::GenericTypeInfo> kType = {"GL/Framebuffer", {"nf7::DirItem"}}; +}; + } } // namespace nf7 @@ -970,4 +1093,8 @@ NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::Texture::Comp); NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::Shader::Type); +NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::VertexArray::AttrType); + +NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::Framebuffer::Slot); + } // namespace yas::detail