├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.ja.md ├── CODE_OF_CONDUCT.md ├── CODE_OF_CONDUCT.zh.md ├── CONTRIBUTING.md ├── CONTRUBUTORS.md ├── LICENSE ├── README.ja.md ├── README.md ├── README.zh.md ├── SECURITY.md ├── dub.sdl ├── modules └── inochi2d-gl │ ├── dub.sdl │ ├── shaders │ └── gl31 │ │ ├── basic │ │ ├── anim.vert │ │ ├── basic-mask.frag │ │ ├── basic-stage1.frag │ │ ├── basic-stage2.frag │ │ ├── basic.frag │ │ ├── basic.vert │ │ ├── composite-mask.frag │ │ ├── composite.frag │ │ └── composite.vert │ │ ├── lighting.frag │ │ ├── mask.frag │ │ ├── mask.vert │ │ ├── scene.frag │ │ └── scene.vert │ └── source │ └── inochi2d │ └── gl │ └── package.d ├── source └── inochi2d │ ├── core │ ├── draw │ │ ├── blending.d │ │ ├── cmd.d │ │ ├── list.d │ │ ├── package.d │ │ └── render.d │ ├── expr │ │ ├── compiler │ │ │ ├── builder.d │ │ │ └── package.d │ │ └── vm │ │ │ ├── opcodes.d │ │ │ ├── package.d │ │ │ ├── stack.d │ │ │ ├── value.d │ │ │ └── vm.d │ └── io │ │ ├── obj.d │ │ ├── package.d │ │ ├── serializer.d │ │ └── tree │ │ ├── ctx.d │ │ ├── package.d │ │ └── value.d │ ├── ffi │ ├── package.d │ ├── puppet.d │ └── scene.d │ ├── package.d │ ├── puppet │ ├── node.d │ ├── package.d │ ├── puppet.d │ └── scene.d │ └── ver.d └── vcvars.ps1 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [LunaTheFoxgirl] 2 | patreon: LunaFoxgirlVT -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report an issue with Inochi2D 3 | title: "[BUG]" 4 | labels: ["Bug", "Triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **NOTE** _If the bug pertains to Inochi Creator or Inochi Session, please go to their respective issue pages!_ 10 | Thanks for taking the time to fill out this bug report! 11 | - type: checkboxes 12 | id: checkboxes 13 | attributes: 14 | label: Validations 15 | description: Before submitting the bug report, please make sure you do the following 16 | options: 17 | - label: I have checked for similar bug reports and could not find any. 18 | required: true 19 | - type: textarea 20 | id: bug-description 21 | attributes: 22 | label: Describe the bug 23 | description: A clear and concise description of what the bug is, and the behavior you expect instead. If you intend to submit a PR for this issue, tell us in the description. Thanks! 24 | placeholder: I am doing ... What I expect is ... What is actually happening is ... 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: reproduction 29 | attributes: 30 | label: Reproduction 31 | description: Steps to reproduce the behavior 32 | placeholder: | 33 | 1. Go to '...' 34 | 2. Click on '...' 35 | 3. Scroll down to '...' 36 | 4. See error 37 | validations: 38 | required: true 39 | - type: dropdown 40 | id: sys-arch 41 | attributes: 42 | label: System Architecture 43 | options: 44 | - x86 / x86_64 45 | - arm64 / aarch64 46 | - other 47 | - type: dropdown 48 | id: sys-os 49 | attributes: 50 | label: Operating System 51 | options: 52 | - Windows 53 | - macOS 54 | - Linux 55 | - OS-less 56 | - type: input 57 | id: version 58 | attributes: 59 | label: Version 60 | description: The version of Inochi2D used, or the branch name and commit hash if you are using a cloned branch. 61 | - type: textarea 62 | id: additional-context 63 | attributes: 64 | label: Additional Context 65 | description: | 66 | Any other context or screenshots about the issue here. 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discord Chat 4 | url: https://discord.com/invite/abnxwN6r9v 5 | about: Ask questions and discuss with other Inochi2D users in real time. 6 | - name: Inochi2D SDK Documentation 7 | url: https://docs.inochi2d.com/en/latest/inochi2d/about.html 8 | about: Latest documentation about the SDK 9 | - name: Inochi2D Technical Specification 10 | url: https://docs.inochi2d.com/en/latest/spec/inp/index.html 11 | about: Inochi2D Technical Specification 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for Inochi2D 3 | title: "[Feature Request]" 4 | labels: ["Enhancement", "Triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **NOTE** _If the feature request pertains to Inochi Creator or Inochi Session, please go to their respective issue pages!_ 10 | Thanks for your interest in the project and taking the time to fill out this feature request! 11 | - type: checkboxes 12 | id: checkboxes 13 | attributes: 14 | label: Validations 15 | description: Before submitting the feature request, please make sure you do the following 16 | options: 17 | - label: I have checked for similar feature requests and could not find any. 18 | required: true 19 | - label: I have made sure this is not an already-existing feature. 20 | required: true 21 | - type: textarea 22 | id: feature-description 23 | attributes: 24 | label: Description 25 | description: | 26 | Clear and concise description of the problem being solved. 27 | Please make the reason and usecases as detailed as possible. 28 | If you intend to submit a PR for this issue, tell us in the description. 29 | Thanks! 30 | placeholder: When using Inochi2D, I would like to be able to [goal / wish] so that [benefit]. 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: suggested-solution 35 | attributes: 36 | label: Suggested solution 37 | description: | 38 | A possible implementation to solve the problem. 39 | If you have technical knowledge, please also take in to account how the solution would work in inochi2d-c. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: alternative 44 | attributes: 45 | label: Alternative solution 46 | description: | 47 | A different possible solution to solve the problem. 48 | - type: textarea 49 | id: additional-context 50 | attributes: 51 | label: Additional Context 52 | description: | 53 | Any other context or screenshots about the issue here. 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /libinochi2d 6 | libinochi2d.so 7 | libinochi2d.dylib 8 | inochi2d.dll 9 | libinochi2d.a 10 | inochi2d.lib 11 | libinochi2d-test-* 12 | *.exe 13 | *.o 14 | *.obj 15 | *.lst 16 | *.png 17 | 18 | # Ignore selections to avoid useless stuff in repo 19 | dub.selections.json 20 | 21 | # Basic Renderer Example 22 | examples/basicrender/basicrender 23 | basicrender.so 24 | basicrender.dylib 25 | basicrender.dll 26 | basicrender.a 27 | basicrender.lib 28 | basicrender-test-* 29 | !logo.png 30 | 31 | out/ 32 | 33 | app.d -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(Windows) Launch", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "enter program name, for example ${workspaceFolder}/a.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "console": "externalTerminal" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.ja.md: -------------------------------------------------------------------------------- 1 | 2 | # コントリビューター行動規範 3 | 4 | ## 私たちの約束 5 | 6 | 私たちはオープンかつ友好的なコミュニティーを育成するために、 7 | コントリビューターやメンテナーとして、年齢、体型、障碍、民族性、 8 | 性自認および性別表現、経験レベル、国籍、個人の容姿、人種、信仰、 9 | 性的同一性および指向に関わりなく、私たちのプロジェクトやコミュニティー 10 | への参加を誰にとっても嫌がらせのない体験にすることを誓います。 11 | 12 | ## 私たちの標準 13 | 14 | 前向きな環境を作り上げることに貢献する振る舞いの例: 15 | 16 | * 友好的かつ男女包括用語の使用 17 | * 異なる観点や経験の尊重 18 | * 建設的批判の率直な受容 19 | * コミュニティーにとっての最善への集中 20 | * 他のコミュニティーメンバーへの共感 21 | 22 | 参加者による容認できない行動の例: 23 | 24 | * 性的な意味を含む言葉や画像、相手の意思に反した性的関心や接近 25 | * あおり、侮辱的または軽蔑的なコメント、個人攻撃や政治攻撃 26 | * 公的または私的な嫌がらせ 27 | * 住所、メールアドレスなど、他者のプライベート情報の明示的な許可なき公開 28 | * 職場において合理的に不適切であると考えられる他の行為 29 | 30 | ## 私たちの責任 31 | 32 | プロジェクトのメンテナーは、許容できる行動の基準を明確にすることに 33 | 責任があります。また、何かしらの許容できない行動に対応する、 34 | 適切かつ公平な是正処置をとることが期待されています。 35 | 36 | プロジェクトのメンテナーは、この行動規範に沿っていない、 37 | コメント、コミット、コード、wiki編集、issue、その他の貢献を 38 | 削除、編集、拒否する権利と義務を有します。 39 | また、他の不適切、脅迫的、攻撃的、嫌がらせと考えられる行動を取った 40 | コントリビューターを一時的もしくは恒久的に追放する権利と義務を有します。 41 | 42 | ## 適用範囲 43 | 44 | この行動規範は、個人がプロジェクトやそのコミュニティーを代表するとき、 45 | プロジェクト内と公共空間の両方において適用されます。プロジェクトや 46 | コミュニティーを代表する例として、プロジェクトの公式メールアドレスの 47 | 使用、ソーシャルメディアの公式アカウント経由の投稿、指名された代表 48 | としてのオンラインやオフラインのイベントにおける行動があります。 49 | プロジェクトを代表することは、プロジェクトのメンテナーにより、 50 | さらに定義され明確化される可能性があります。 51 | 52 | ## 執行 53 | 54 | 暴言、嫌がらせ、またはそれ以外の受け入れられない行動は、 55 | [Discordサーバ](https://discord.com/invite/abnxwN6r9v)上での開発者チームへの連絡、 56 | もしくはプロジェクト管理者のルナ([conduct@inochi2d.com](mailto:conduct@inochi2d.com))へのメールでの連絡によって、 57 | プロジェクトチームに報告される可能性があります。すべての苦情は、 58 | レビュー、調査され、必要かつ適切と判断された対応がとられます。 59 | プロジェクトチームは、事象の報告者に関する守秘義務があります。 60 | 具体的な執行に関する詳細が別途設定されているかもしれません。 61 | 62 | この行動規範に誠意を持って従うまたは執行することができない 63 | プロジェクトのメンテナーは、プロジェクトをリードしている他のメンバー 64 | の判断により、一時的または恒久的な影響を受けることがあります。 65 | 66 | ## 帰属 67 | 68 | この行動規範は 69 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html にある 70 | [Contributor Covenant][homepage] バージョン 1.4 に適合しています。 71 | 72 | [homepage]: https://www.contributor-covenant.org -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others’ private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting members of the Moderation team on the Discord Server or via our email ([conduct@inochi2d.com](mailto:conduct@inochi2d.com)). 38 | 39 | All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project leadership is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 40 | 41 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. 42 | 43 | ## Attribution 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) 46 | 47 | For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) 48 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.zh.md: -------------------------------------------------------------------------------- 1 | 2 | # 貢獻者公約 3 | 4 | ## 我們的承諾 5 | 6 | 為了促進一個開放透明且受歡迎的環境,我們作為貢獻者和維護者保證,無論年齡、種族、民族、性別認同和表達、體型、殘疾、經驗水平、國籍、個人表現、宗教或性別取向,在我們的專案以及社群的參與者都有不被騷擾的體驗。 7 | 8 | ## 我們的準則 9 | 10 | 舉例來說有助於創造正面環境的行為包括: 11 | 12 | * 使用歡迎和包容性語言 13 | * 尊重不同的觀點和經驗 14 | * 優雅地接受建設性批評 15 | * 關注在對於社群最好的事情上 16 | * 對其他社群成員的表現友善 17 | 18 | 舉例來說身為參與者不能接受的行為包括: 19 | 20 | * 使用與性有關的言語或是圖像,以及不受歡迎的性騷擾 21 | * 酸民/反串/釣魚行為或進行侮辱/貶損的評論,人身攻擊及政治攻擊 22 | * 公開或私下的騷擾 23 | * 未經許可地發布他人的個人資料,例如住址或是電子地址 24 | * 其他可以被合理地認定為不恰當或者違反職業操守的行為 25 | 26 | ## 我們的責任 27 | 28 | 專案維護者有責任為"可接受的行為"準則做出詮釋,以及對已發生的不被接受的行為採取恰當且公平的糾正措施。 29 | 30 | 專案維護者有權力及責任去刪除、編輯、拒絕與本行為準則有所違背的評論 (comments)、提交 (commits)、程式碼、wiki 編輯、問題 (issues) 和其他貢獻,以及專案維護者可暫時或永久性的禁止任何他們認為有不適當、威脅、冒犯、有害行為的貢獻者。 31 | 32 | ## 使用範圍 33 | 34 | 當一個人代表該專案或是其社群時,本行為準則適用於其專案平台和公共平台。 35 | 36 | 代表專案或是社群的情況,舉例來說包括使用官方專案的電子郵件地址、通過官方的社群媒體帳號發布或線上或線下事件中擔任指定代表。 37 | 38 | 該專案的呈現方式可由其專案維護者進行進一步的定義及解釋。 39 | 40 | ## 強制執行 41 | 42 | 可以透過[conduct@inochi2d.com](mailto:conduct@inochi2d.com),來聯繫專案團隊來報告濫用、騷擾或其他不被接受的行為。 43 | 44 | 任何維護團隊認為有必要且適合的所有投訴都將進行審查及調查,並做出相對應的回應。專案小組有對事件回報者有保密的義務。具體執行的方針近一步細節可能會單獨公佈。 45 | 46 | 沒有真誠的遵守或是執行本行為準則的專案維護人員,可能會因專案領導人或是其他成員的決定,暫時或是永久的取消其身份。 47 | 48 | ## 來源 49 | 50 | 本行為準則改編自[貢獻者公約][首頁],版本 1.4 51 | 可在此觀看https://www.contributor-covenant.org/zh-tw/version/1/4/code-of-conduct.html 52 | 53 | [首頁]: https://www.contributor-covenant.org -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Inochi2D 2 | Thank you for your interest in contributing to the Inochi2D project! 3 | 4 | Before getting in contact, please familiarize yourself with our [code of conduct](https://github.com/Inochi2D/inochi2d/blob/main/CODE_OF_CONDUCT.md). 5 | 6 | ## Contents 7 | * [Ask for help](#ask-for-help) 8 | * [Report a bug](#report-a-bug) 9 | * [Suggest a new feature](#suggest-a-new-feature) 10 | * [Build and test the code](#build-and-test-the-code) 11 | * [Contribute code changes](#contribute-code-changes) 12 | * [Contribute documentation](#contribute-documentation) 13 | * [Contribute test material](#contribute-test-material) 14 | * [Join the community](#join-the-community) 15 | * [Donate funds](#donate-funds) 16 | 17 | ## Ask for help 18 | There are a number of ways to request support: 19 | 20 | * Raise an issue on our [Github project](https://github.com/Inochi2D/inochi2d) using the "question" template 21 | * Reach out to us on Luna's [Discord server](https://discord.gg/tNfMGgJ9Fn) 22 | * Send me a message on social media ([Twitter](https://twitter.com/Clipsey5)) 23 | 24 | [Return to top](#top) 25 | 26 | ## Report a bug 27 | There are a number of ways to report a bug: 28 | 29 | * Raise an issue on our [Github project](https://github.com/Inochi2D/inochi2d) using the "bug report" template 30 | * Reach out to us on Luna's [Discord server](https://discord.gg/tNfMGgJ9Fn) 31 | * Send me a message on social media ([Twitter](https://twitter.com/Clipsey5)) 32 | 33 | [Return to top](#top) 34 | 35 | ## Suggest a new feature 36 | There are a number of ways to request a new feature: 37 | 38 | * Raise an issue on our [Github project](https://github.com/Inochi2D/inochi2d) using the "feature request" template 39 | * Reach out to us on Luna's [Discord server](https://discord.gg/tNfMGgJ9Fn) 40 | * Send me a message on social media ([Twitter](https://twitter.com/Clipsey5)) 41 | 42 | [Return to top](#top) 43 | 44 | ## Build and test the code 45 | You can find full instructions for this specific release in the `README.md` file provided with this source code. 46 | 47 | [Return to top](#top) 48 | 49 | ## Contribute code changes 50 | Raise a pull request on our [Github project](https://github.com/Inochi2D/inochi2d) and follow the template. 51 | 52 | #### NOTE 53 | Inochi2D does not support compilation with the OpenD language, make sure any PRs for the Inochi2D library compiles with the official D language compilers (LDC2, DMD, GDC). 54 | 55 | Which can be downloaded from [the official website](https://dlang.org). 56 | 57 | ### Notes 58 | * We do not accept contributions from people who work for/at Live2D inc. and affiliates. 59 | * We will not accept pull requests that aim to support other 2d puppet formats within Inochi2D. 60 | * Please do not submit code that is stolen from propriatary software. 61 | 62 | [Return to top](#top) 63 | 64 | ## Contribute documentation 65 | To change the code-level documentation, raise a pull request on our [Github project](https://github.com/Inochi2D/inochi2d) and follow the template. 66 | 67 | [Return to top](#top) 68 | 69 | ## Contribute test material 70 | We'll be happy if any artists for vtuber models donate any old model textures for testing, doing so can be done via [DMing Luna on Twitter](https://twitter.com/Clipsey5). 71 | Note you still retain all rights to your art/models and they will only be used for internal testing, if you at one point want to revoke your art as test material just tell Luna and she'll delete the textures. 72 | 73 | ## Join the community 74 | We recommend you familiarize yourself with our [code of conduct](https://github.com/Inochi2D/inochi2d/blob/main/CODE_OF_CONDUCT.md). 75 | 76 | Once you have done so, you are welcome to join us on [Luna's Discord Server](https://discord.gg/tNfMGgJ9Fn). That communication channel is where our internal discussions happen and project decisions are made. 77 | 78 | [Return to top](#top) 79 | 80 | ## Donate funds 81 | If you would like to help Luna cover the costs of running the Inochi2D project, you can do so here: https://www.patreon.com/clipsey 82 | 83 | [Return to top](#top) 84 | -------------------------------------------------------------------------------- /CONTRUBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors for Inochi2D 2 | This document lists the people who've contributed to the development of Inochi2D (the library) 3 | 4 | ## Code 5 | - Luna the Foxgirl (Main Developer) 6 | 7 | ## Translation 8 | Japanese Translation: 9 | - [Veertig Nix](https://twitter.com/40Nix) (Documentation and README translation) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Inochi2D Project 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Inochi2D 6 | [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dclipsey%26type%3Dpatrons&style=for-the-badge)](https://patreon.com/clipsey) 7 | [![Discord](https://img.shields.io/discord/855173611409506334?label=Community&logo=discord&logoColor=FFFFFF&style=for-the-badge)](https://discord.com/invite/abnxwN6r9v) 8 | 9 | Inochi2Dはリアルタイム2Dパペットアニメーションライブラリで、Inochi2DPuppetスタンダードのリファレンス実装です。Inochi2Dはレイヤーに分割された絵から作成されたメッシュを、実行時にパラメーターに基づいて変形させ、視聴者に奥行きと動きを感じさせます。 10 | 11 |   12 | 13 | https://user-images.githubusercontent.com/7032834/166389697-02eeeedb-6a44-4570-9254-f6aa4f095300.mp4 14 | 15 | *Beta 0.7.2を使用。VTuber:[LunaFoxgirlVT](https://twitter.com/LunaFoxgirlVT)、イラストレーター:[kpon](https://twitter.com/kawaiipony2)* 16 | 17 |   18 | 19 | # モデラーまたはVTuber様へ 20 | あなたがモデラーなら、ぜひこちらをご覧ください。 21 | 開発中の公式Inochi2Dモデリングアプリ [Inochi Creator](https://github.com/Inochi2D/inochi-creator) 22 | 23 | あなたがVTuberなら、ぜひこちらをご覧ください。 24 | [Inochi Session](https://github.com/Inochi2D/inochi-session) 25 | 26 | 注:このリポジトリはスタンダード専用です。エンドユーザー向けのものではありません。 27 | 28 |   29 | 30 | # ドキュメント 31 | 現在、仕様と公式ツール群に関するドキュメントの執筆中です。[こちら](https://docs.inochi2d.com)から公式ドキュメントをご覧いただけます。 32 | 33 |   34 | 35 | # サポート対象のプラットフォーム 36 | 37 | このリファレンス実装は、動作にOpenGL 3.1コンテキストを必要とします。また、OpenGL 3.1(または以降のバージョン)のコンテキストが確立された後に`inInit`が呼び出されるべきです。 38 | 39 | 私たちは、開発者が任意のバックエンドを接続できるように、レンダリング機能をフロントエンドから分離することに取り組むつもりです。私たちは、D言語以外からこのライブラリを使用する方法として[inochi2d-c](https://github.com/Inochi2D/inochi2d-c)を提供し、さらに2つ目のワークグループが、[Inox2D](https://github.com/Inochi2D/inox2d)でInochi2D仕様の純粋なRust実装を開発しています。 40 | 41 |   42 | 43 | --- 44 | 45 | Inochi2Dのロゴは[James Daniel](https://twitter.com/rakujira)氏によってデザインされました。 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [日本語](https://github.com/Inochi2D/inochi2d/blob/main/README.ja.md) 6 | [简体中文](https://github.com/Inochi2D/inochi2d/blob/main/README.zh.md) 7 | 8 | # Inochi2D 9 | [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dclipsey%26type%3Dpatrons&style=for-the-badge)](https://patreon.com/clipsey) 10 | [![Discord](https://img.shields.io/discord/855173611409506334?label=Community&logo=discord&logoColor=FFFFFF&style=for-the-badge)](https://discord.com/invite/abnxwN6r9v) 11 | 12 | Inochi2D is a library for realtime 2D puppet animation and the reference implementation of the Inochi2D Puppet standard. Inochi2D works by deforming 2D meshes created from layered art at runtime based on parameters, this deformation tricks the viewer in to seeing 3D depth and movement in the 2D art. 13 | 14 |   15 | 16 | 17 | https://user-images.githubusercontent.com/7032834/166389697-02eeeedb-6a44-4570-9254-f6aa4f095300.mp4 18 | 19 | *Video from Beta 0.7.2, [LunaFoxgirlVT](https://twitter.com/LunaFoxgirlVT), model art by [kpon](https://twitter.com/kawaiipony2)* 20 | 21 |   22 | 23 | # Looking for 0.8? 24 | Inochi2D is undergoing what essentially amounts to a rewrite, if you're looking for Inochi2D 0.8 check the v0_8 branch! 25 | 26 | # For Riggers and VTubers 27 | If you're a model rigger you may want to check out [Inochi Creator](https://github.com/Inochi2D/inochi-creator), the official Inochi2D rigging app in development. 28 | If you're a VTuber you may want to check out [Inochi Session](https://github.com/Inochi2D/inochi-session). 29 | This repository is purely for the standard and is not useful if you're an end user. 30 | 31 |   32 | 33 | # Documentation 34 | Documentation is currently in the process of being written for the spec and the official tools. You can find the official documentation page [here](https://docs.inochi2d.com). 35 | 36 |   37 | 38 | # Supported platforms 39 | Inochi2D is a "bring your own renderer" API, we provide a OpenGL 3.1 backend to get you started easily and to work as a reference on how a renderer can be implemented. 40 | To use the OpenGL renderer call `inRendererInitGL` during initialization of Inochi2D, a OpenGL 3.1 core context needs to be present. 41 | 42 | We provide [inochi2d-c](https://github.com/Inochi2D/inochi2d-c) as a way to use this library from non-D languages and we will be providing a layer to allow non-D languages to create rendering backends, additionally a second workgroup is making a pure Rust implementation of the Inochi2D specification over at [Inox2D](https://github.com/Inochi2D/inox2d). 43 | 44 | #### NOTE 45 | Inochi2D does not support compilation with the OpenD language. Only the [the official D language](https://dlang.org) and compilers are supported. 46 | 47 |   48 | 49 | 50 | ## Special Thanks 51 | 52 | This project is funded through [NGI0 Entrust](https://nlnet.nl/entrust), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/Inochi2D). 53 | 54 | [NLnet foundation logo](https://nlnet.nl) 55 | 56 | --- 57 | 58 | The Inochi2D logo was designed by [James Daniel](https://twitter.com/rakujira) 59 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Inochi2D 6 | 7 | 在 Patreon 上提供资金支持: 8 | [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dclipsey%26type%3Dpatrons&style=for-the-badge)](https://patreon.com/clipsey) 9 | Discord 社区: 10 | [![Discord](https://img.shields.io/discord/855173611409506334?label=Community&logo=discord&logoColor=FFFFFF&style=for-the-badge)](https://discord.com/invite/abnxwN6r9v) 11 | 12 | Inochi2D(当前代码仓库)是一个实时二维皮套动画库,也是 Inochi2D 标准的参考实现。Inochi2D 的基本工作原理是,在运行时,根据给定的参数,对绑定在分层美术资源上的2D网格进行变形。这样的变形使得观众可以在二维图形中体验到三维的深度与动画效果。 13 | 14 |   15 | 16 | https://user-images.githubusercontent.com/7032834/166389697-02eeeedb-6a44-4570-9254-f6aa4f095300.mp4 17 | 18 | *来自 Beta 0.7.2 版本的视频,作者 [LunaFoxgirlVT](https://twitter.com/LunaFoxgirlVT), 画师 [kpon](https://twitter.com/kawaiipony2)* 19 | 20 |   21 | 22 | # 如果您是模型师或虚拟皮套使用者: 23 | 24 | 模型师可能会感兴趣 [Inochi Creator](https://github.com/Inochi2D/inochi-creator), 这是正在开发中的 Inochi2D 官方建模软件。 25 | 虚拟皮套使用者可能会感兴趣 [Inochi Session](https://github.com/Inochi2D/inochi-session)。 26 | 当前代码仓库是为了 Inochi2D 标准而存在的,对最终用户可能用处有限。 27 | 28 |   29 | 30 | # 文档 31 | 有关标准和官方工具的文档目前正在编写和翻译过程中。官方文档在[这里](https://docs.inochi2d.com)。 32 | 33 |   34 | 35 | # 设备支持情况 36 | 当前仓库里的实现需要在一个 OpenGL 3.1 Context 中运行。`inInit`函数应当在 OpenGL 3.1 (或更高版本)Context *建立后* 被调用。 37 | 38 | 我们计划从前端代码中分离出渲染部分,这样开发者们就能接入自己的渲染后端。 39 | 40 | 我们提供 [inochi2d-c](https://github.com/Inochi2D/inochi2d-c) 作为从非 D 的语言中调用本库的接口。 41 | 42 | 额外地,另一个开发组正在编写一个 Inochi2D 标准的纯 Rust 实现,代码仓库在[这里](https://github.com/Inochi2D/inox2d)。 43 | 44 |   45 | 46 | 47 | --- 48 | 49 | Inochi2D 的标志是 [James Daniel](https://twitter.com/rakujira) 设计的。 50 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Inochi2D versions are supported with updates from the current stable version, and the in-production next version (nightly builds and such) 6 | If a vulnerability is sufficiently severe we may backport fixes to earlier versions on request. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 0.6.x | :x: | 11 | | 0.7.x | :x: | 12 | | 0.8.x | :white_check_mark: | 13 | | 0.9.x* | :white_check_mark: | 14 | 15 | \* = Version currently in active development 16 | 17 | ## Reporting a Vulnerability 18 | 19 | Vulnerabilies should be reported as encrypted emails to our address for the purpose, security@inochi2d.com 20 | 21 | The following PGP key should be used for encryption 22 | ``` 23 | -----BEGIN PGP PUBLIC KEY BLOCK----- 24 | 25 | xjMEZAJcVRYJKwYBBAHaRw8BAQdA7VAqX1faN1QGLFv08bbPqQN9Rv9dYKQu 26 | hmLqtakmEGfNLXNlY3VyaXR5QGlub2NoaTJkLmNvbSA8c2VjdXJpdHlAaW5v 27 | Y2hpMmQuY29tPsKMBBAWCgA+BQJkAlxVBAsJBwgJEBMKoVRwErm1AxUICgQW 28 | AAIBAhkBAhsDAh4BFiEES4zizv11eFgNQ2b2EwqhVHASubUAAKyGAP4xr5Cx 29 | sBAnIxKYcTIvsXHFBpjlaeNisdegezinIE6/XAD7BTOlXJ7E51rvzTyVW+gd 30 | sIDv2PNPqsiS9S+Fn7/6UQHOOARkAlxVEgorBgEEAZdVAQUBAQdALNMMkSZr 31 | VuyvQIpK0qwbROME5Q16O6a7eJan17yhazQDAQgHwngEGBYIACoFAmQCXFUJ 32 | EBMKoVRwErm1AhsMFiEES4zizv11eFgNQ2b2EwqhVHASubUAAC5lAQDfM7oT 33 | rRLWq5943RE7wORPMmi7qVvbOLR4sI0KuydZlgD+PZ0zu8Yf5D8tyKplRS1g 34 | YTGSGInbgh4rQnXle4M/nQw= 35 | =xGFO 36 | -----END PGP PUBLIC KEY BLOCK----- 37 | ``` 38 | 39 | You may also report vulnerabilties via the [Security Advisories](https://github.com/Inochi2D/inochi2d/security/advisories/new) feature, note that this feature may not be used indefinitely and we would highly prefer an email! 40 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "inochi2d" 2 | description "The Official Inochi2D SDK" 3 | authors "Luna Nielsen" 4 | copyright "Copyright © 2020-2024, Inochi2D Project" 5 | license "BSD 2-clause" 6 | 7 | dependency "imagefmt" version="~>2.1.0" 8 | dependency "silly" version="~>1.1.1" 9 | dependency "numem" version="~>0.20.0" 10 | dependency "inmath" version="~>1.0.5" 11 | dependency "fghj" version="~>1.0.2" 12 | dependency "intel-intrinsics" version="1.11.19" 13 | 14 | targetPath "out/" 15 | 16 | // Default config 17 | configuration "build" { 18 | targetType "dynamicLibrary" 19 | excludedSourceFiles "inochi2d/demo/*" 20 | } 21 | 22 | // Build w/ minimal runtime 23 | configuration "build-tinyd" { 24 | targetType "dynamicLibrary" 25 | excludedSourceFiles "inochi2d/demo/*" 26 | 27 | // TODO: Implement 28 | } 29 | 30 | configuration "demo" { 31 | targetType "executable" 32 | } 33 | 34 | // Update ver.d 35 | configuration "updateVersion" { 36 | targetType "none" 37 | preBuildCommands "dub run gitver -- --prefix IN --file source/inochi2d/ver.d --mod inochi2d.ver --appname Inochi2D" 38 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/dub.sdl: -------------------------------------------------------------------------------- 1 | name "inochi2d-gl" 2 | -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/anim.vert: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | uniform mat4 mvp; 9 | uniform vec2 offset; 10 | 11 | layout(location = 0) in vec2 verts; 12 | layout(location = 1) in vec2 uvs; 13 | layout(location = 2) in vec2 deform; 14 | 15 | uniform vec2 splits; 16 | uniform float animation; 17 | uniform float frame; 18 | 19 | out vec2 texUVs; 20 | 21 | void main() { 22 | gl_Position = mvp * vec4(verts.x-offset.x+deform.x, verts.y-offset.y+deform.y, 0, 1); 23 | texUVs = vec2((uvs.x/splits.x)*frame, (uvs.y/splits.y)*animation); 24 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/basic-mask.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | out vec4 outColor; 10 | 11 | uniform sampler2D tex; 12 | uniform float threshold; 13 | 14 | void main() { 15 | vec4 color = texture(tex, texUVs); 16 | if (color.a <= threshold) discard; 17 | outColor = vec4(1, 1, 1, 1); 18 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/basic-stage1.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | 7 | ADVANCED BLENDING - STAGE 1 8 | */ 9 | #version 330 10 | 11 | // Advanced blendig mode enable 12 | #ifdef GL_KHR_blend_equation_advanced 13 | #extension GL_KHR_blend_equation_advanced : enable 14 | #endif 15 | 16 | #ifdef GL_ARB_sample_shading 17 | #extension GL_ARB_sample_shading : enable 18 | #endif 19 | 20 | in vec2 texUVs; 21 | 22 | // Handle layout qualifiers for advanced blending specially 23 | #ifdef GL_KHR_blend_equation_advanced 24 | layout(blend_support_all_equations) out; 25 | layout(location = 0) out vec4 outAlbedo; 26 | #else 27 | layout(location = 0) out vec4 outAlbedo; 28 | #endif 29 | 30 | uniform sampler2D albedo; 31 | 32 | uniform float opacity; 33 | uniform vec3 multColor; 34 | uniform vec3 screenColor; 35 | 36 | void main() { 37 | // Sample texture 38 | vec4 texColor = texture(albedo, texUVs); 39 | 40 | // Screen color math 41 | vec3 screenOut = vec3(1.0) - ((vec3(1.0)-(texColor.xyz)) * (vec3(1.0)-(screenColor*texColor.a))); 42 | 43 | // Multiply color math + opacity application. 44 | outAlbedo = vec4(screenOut.xyz, texColor.a) * vec4(multColor.xyz, 1) * opacity; 45 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/basic-stage2.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | 7 | ADVANCED BLENDING - STAGE 2 8 | */ 9 | #version 330 10 | in vec2 texUVs; 11 | 12 | layout(location = 1) out vec4 outEmissive; 13 | layout(location = 2) out vec4 outBump; 14 | 15 | uniform sampler2D albedo; 16 | uniform sampler2D emissive; 17 | uniform sampler2D bumpmap; 18 | 19 | uniform float opacity; 20 | uniform vec3 multColor; 21 | uniform vec3 screenColor; 22 | uniform float emissionStrength = 1; 23 | 24 | vec4 screen(vec3 tcol, float a) { 25 | return vec4(vec3(1.0) - ((vec3(1.0)-tcol) * (vec3(1.0)-(screenColor*a))), a); 26 | } 27 | 28 | void main() { 29 | // Sample texture 30 | vec4 texColor = texture(albedo, texUVs); 31 | vec4 emiColor = texture(emissive, texUVs); 32 | vec4 bmpColor = texture(bumpmap, texUVs); 33 | 34 | vec4 mult = vec4(multColor.xyz, 1); 35 | 36 | // Out color math 37 | vec4 emissionOut = screen(emiColor.xyz, texColor.a) * mult * emissionStrength; 38 | 39 | // Emissive 40 | outEmissive = emissionOut * texColor.a; 41 | 42 | // Bumpmap 43 | outBump = bmpColor * texColor.a; 44 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/basic.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | 10 | layout(location = 0) out vec4 outAlbedo; 11 | layout(location = 1) out vec4 outEmissive; 12 | layout(location = 2) out vec4 outBump; 13 | 14 | uniform sampler2D albedo; 15 | uniform sampler2D emissive; 16 | uniform sampler2D bumpmap; 17 | 18 | uniform float opacity; 19 | uniform vec3 multColor; 20 | uniform vec3 screenColor; 21 | uniform float emissionStrength = 1; 22 | 23 | vec4 screen(vec3 tcol, float a) { 24 | return vec4(vec3(1.0) - ((vec3(1.0)-tcol) * (vec3(1.0)-(screenColor*a))), a); 25 | } 26 | 27 | void main() { 28 | // Sample texture 29 | vec4 texColor = texture(albedo, texUVs); 30 | vec4 emiColor = texture(emissive, texUVs); 31 | vec4 bmpColor = texture(bumpmap, texUVs); 32 | 33 | vec4 mult = vec4(multColor.xyz, 1); 34 | 35 | // Out color math 36 | vec4 albedoOut = screen(texColor.xyz, texColor.a) * mult; 37 | vec4 emissionOut = screen(emiColor.xyz, texColor.a) * mult * emissionStrength; 38 | 39 | // Albedo 40 | outAlbedo = albedoOut * opacity; 41 | 42 | // Emissive 43 | outEmissive = emissionOut * outAlbedo.a; 44 | 45 | // Bumpmap 46 | outBump = bmpColor * outAlbedo.a; 47 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/basic.vert: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | uniform mat4 mvp; 9 | uniform vec2 offset; 10 | 11 | layout(location = 0) in vec2 verts; 12 | layout(location = 1) in vec2 uvs; 13 | layout(location = 2) in vec2 deform; 14 | 15 | out vec2 texUVs; 16 | 17 | void main() { 18 | gl_Position = mvp * vec4(verts.x-offset.x+deform.x, verts.y-offset.y+deform.y, 0, 1); 19 | texUVs = uvs; 20 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/composite-mask.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | out vec4 outColor; 10 | 11 | uniform sampler2D tex; 12 | uniform float threshold; 13 | uniform float opacity; 14 | 15 | void main() { 16 | vec4 color = texture(tex, texUVs) * vec4(1, 1, 1, opacity); 17 | if (color.a <= threshold) discard; 18 | outColor = vec4(1, 1, 1, 1); 19 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/composite.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | 10 | layout(location = 0) out vec4 outAlbedo; 11 | layout(location = 1) out vec4 outEmissive; 12 | layout(location = 2) out vec4 outBump; 13 | 14 | uniform sampler2D albedo; 15 | uniform sampler2D emissive; 16 | uniform sampler2D bumpmap; 17 | 18 | uniform float opacity; 19 | uniform vec3 multColor; 20 | uniform vec3 screenColor; 21 | 22 | vec4 screen(vec3 tcol, float a) { 23 | return vec4(vec3(1.0) - ((vec3(1.0)-tcol) * (vec3(1.0)-(screenColor*a))), a); 24 | } 25 | 26 | void main() { 27 | // Sample texture 28 | vec4 texColor = texture(albedo, texUVs); 29 | vec4 emiColor = texture(emissive, texUVs); 30 | vec4 bmpColor = texture(bumpmap, texUVs); 31 | 32 | vec4 mult = vec4(multColor.xyz, 1); 33 | 34 | // Out color math 35 | vec4 albedoOut = screen(texColor.xyz, texColor.a) * mult; 36 | vec4 emissionOut = screen(emiColor.xyz, texColor.a) * mult; 37 | 38 | // Albedo 39 | outAlbedo = albedoOut * opacity; 40 | 41 | // Emissive 42 | outEmissive = emissionOut; 43 | 44 | // Bumpmap 45 | outBump = bmpColor; 46 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/basic/composite.vert: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | uniform mat4 mvp; 9 | layout(location = 0) in vec2 verts; 10 | layout(location = 1) in vec2 uvs; 11 | 12 | out vec2 texUVs; 13 | 14 | void main() { 15 | gl_Position = vec4(verts, 0, 1); 16 | texUVs = uvs; 17 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/lighting.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | 10 | layout(location = 0) out vec4 outAlbedo; 11 | layout(location = 1) out vec4 outEmissive; 12 | layout(location = 2) out vec4 outBump; 13 | 14 | uniform vec3 ambientLight; 15 | uniform vec2 fbSize; 16 | 17 | uniform sampler2D albedo; 18 | uniform sampler2D emissive; 19 | uniform sampler2D bumpmap; 20 | uniform int LOD = 2; 21 | uniform int samples = 25; 22 | 23 | // Gaussian 24 | float gaussian(vec2 i, float sigma) { 25 | return exp(-0.5*dot(i /= sigma, i)) / (6.28*sigma*sigma); 26 | } 27 | 28 | // Bloom texture by blurring it 29 | vec4 bloom(sampler2D sp, vec2 uv, vec2 scale) { 30 | float sigma = float(samples) * 0.25; 31 | vec4 out_ = vec4(0); 32 | int sLOD = 1 << LOD; 33 | int s = samples/sLOD; 34 | 35 | for ( int i = 0; i < s*s; i++ ) { 36 | vec2 d = vec2(i%s, i/s)*float(sLOD) - float(samples)/2.0; 37 | out_ += gaussian(d, sigma) * textureLod( sp, uv + scale * d, LOD); 38 | } 39 | 40 | return out_ / out_.a; 41 | } 42 | 43 | void main() { 44 | 45 | // Bloom 46 | outEmissive = texture(emissive, texUVs)+bloom(emissive, texUVs, 1.0/fbSize); 47 | 48 | // Set color to the corrosponding pixel in the FBO 49 | vec4 light = vec4(ambientLight, 1) + outEmissive; 50 | 51 | outAlbedo = (texture(albedo, texUVs)*light); 52 | outBump = texture(bumpmap, texUVs); 53 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/mask.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | out vec4 outColor; 9 | 10 | void main() { 11 | outColor = vec4(0, 0, 0, 1); 12 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/mask.vert: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | uniform mat4 mvp; 9 | uniform vec2 offset; 10 | layout(location = 0) in vec2 verts; 11 | 12 | out vec2 texUVs; 13 | 14 | void main() { 15 | gl_Position = mvp * vec4(verts.x-offset.x, verts.y-offset.y, 0, 1); 16 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/scene.frag: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | in vec2 texUVs; 9 | out vec4 outColor; 10 | 11 | uniform sampler2D fbo; 12 | 13 | void main() { 14 | // Set color to the corrosponding pixel in the FBO 15 | vec4 color = texture(fbo, texUVs); 16 | outColor = vec4(color.r, color.g, color.b, color.a); 17 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/shaders/gl31/scene.vert: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna Nielsen 6 | */ 7 | #version 330 8 | uniform mat4 mvp; 9 | layout(location = 0) in vec2 verts; 10 | layout(location = 1) in vec2 uvs; 11 | 12 | out vec2 texUVs; 13 | 14 | void main() { 15 | gl_Position = mvp * vec4(verts.x, verts.y, 0, 1); 16 | texUVs = uvs; 17 | } -------------------------------------------------------------------------------- /modules/inochi2d-gl/source/inochi2d/gl/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Inochi2D OpenGL Backend 3 | */ 4 | module inochi2d.gl; 5 | 6 | -------------------------------------------------------------------------------- /source/inochi2d/core/draw/blending.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.draw.blending; 9 | import numem.all; 10 | 11 | /** 12 | Blending modes 13 | */ 14 | enum BlendMode : ubyte { 15 | 16 | // Normal blending mode 17 | Normal, 18 | 19 | // Multiply blending mode 20 | Multiply, 21 | 22 | // Screen 23 | Screen, 24 | 25 | // Overlay 26 | Overlay, 27 | 28 | // Darken 29 | Darken, 30 | 31 | // Lighten 32 | Lighten, 33 | 34 | // Color Dodge 35 | ColorDodge, 36 | 37 | // Linear Dodge 38 | LinearDodge, 39 | 40 | // Add (Glow) 41 | AddGlow, 42 | 43 | // Color Burn 44 | ColorBurn, 45 | 46 | // Hard Light 47 | HardLight, 48 | 49 | // Soft Light 50 | SoftLight, 51 | 52 | // Difference 53 | Difference, 54 | 55 | // Exclusion 56 | Exclusion, 57 | 58 | // Subtract 59 | Subtract, 60 | 61 | // Inverse 62 | Inverse, 63 | 64 | // Destination In 65 | DestinationIn, 66 | 67 | // Clip to Lower 68 | // Special blending mode that clips the drawable 69 | // to a lower rendered area. 70 | ClipToLower, 71 | 72 | // Slice from Lower 73 | // Special blending mode that slices the drawable 74 | // via a lower rendered area. 75 | // Basically inverse ClipToLower 76 | SliceFromLower 77 | } -------------------------------------------------------------------------------- /source/inochi2d/core/draw/cmd.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.draw.cmd; 9 | import inochi2d.core.draw.blending; 10 | import inochi2d.core.draw.render; 11 | import numem.all; 12 | import inmath; 13 | 14 | /** 15 | Command types 16 | */ 17 | enum CommandType { 18 | /// Command is drawing 19 | Draw, 20 | 21 | /// Commanding is blitting framebuffers 22 | Blit 23 | } 24 | 25 | /** 26 | State of the stencil buffer 27 | */ 28 | enum StencilState { 29 | /** 30 | Stencil is turned off 31 | */ 32 | Off, 33 | 34 | /** 35 | Stencil on, is cleared and is being filled 36 | */ 37 | Fill, 38 | 39 | /** 40 | Stenciling is turned on. 41 | */ 42 | On 43 | } 44 | 45 | /** 46 | Flags to be set. 47 | */ 48 | enum DrawFlags : uint { 49 | 50 | /// Nothing special should be performed 51 | None = 0x00, 52 | 53 | /// Tells the render backend to clear the render target. 54 | ClearTarget = 0x01, 55 | } 56 | 57 | /** 58 | A drawing command, equating to one draw call sent to the GPU. 59 | */ 60 | struct DrawCommand { 61 | @nogc: 62 | CommandType type; 63 | 64 | /** 65 | Clipping rectangle to be applied 66 | */ 67 | rect clippingRect; 68 | 69 | /** 70 | How many textures (max 3) that is being rendered from 71 | */ 72 | uint renderSources; 73 | 74 | /** 75 | Texture to be bound in drawing command 76 | */ 77 | InResourceID[3] source; 78 | 79 | /** 80 | Render target of draw command, null if main framebuffer. 81 | */ 82 | InResourceID target; 83 | 84 | /** 85 | State of the stencil buffer. 86 | */ 87 | StencilState stencilState; 88 | 89 | /** 90 | Drawing flags. 91 | */ 92 | DrawFlags flags; 93 | 94 | /** 95 | Blending mode to be set for this command. 96 | */ 97 | BlendMode blending; 98 | 99 | /** 100 | Index offset 101 | */ 102 | uint idxOffset; 103 | 104 | /** 105 | Vertex offset 106 | */ 107 | uint vtxOffset; 108 | 109 | /** 110 | Amount of elements to draw. 111 | */ 112 | uint drawCount; 113 | } -------------------------------------------------------------------------------- /source/inochi2d/core/draw/list.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.draw.list; 9 | import inochi2d.core.draw.cmd; 10 | import inochi2d.core.draw.render; 11 | import numem.all; 12 | import inmath; 13 | 14 | /** 15 | Draw vertices 16 | 17 | TODO: Seperate UV and position for faster updates in normal use? 18 | */ 19 | struct DrawVertex { 20 | vec2 position; 21 | vec2 uv; 22 | } 23 | 24 | /** 25 | How many readings should be kept to determine when to compact 26 | the memory associated with the draw list. 27 | */ 28 | enum IN_DRAW_COMPACT_READINGS = 10; 29 | 30 | /** 31 | The threshold at which the draw list will be compacted. 32 | */ 33 | enum IN_DRAW_COMPACT_THRESHOLD = 5; 34 | 35 | /** 36 | How many vertices should be reserved initially. 37 | */ 38 | enum IN_DRAW_RESERVE = 10_000; 39 | 40 | /** 41 | Draw List which keeps track of drawing commands. 42 | */ 43 | class DrawList { 44 | @nogc: 45 | private: 46 | vector!DrawCommand commands; 47 | 48 | // Vertex buffer 49 | vector!DrawVertex vertices; 50 | 51 | // Index buffer 52 | vector!uint indices; 53 | 54 | // Draw list compactor stats. 55 | size_t[IN_DRAW_COMPACT_READINGS+1] prevIndiceCount; 56 | 57 | public: 58 | 59 | /** 60 | Destructor 61 | */ 62 | ~this() { 63 | destroy(commands); 64 | destroy(vertices); 65 | destroy(indices); 66 | } 67 | 68 | /** 69 | Constructor 70 | */ 71 | this() { 72 | 73 | // To optimize initial rendering before warmup, 74 | // we reserve enough memory for 10k vertices. 75 | this.vertices.reserve(IN_DRAW_RESERVE); 76 | 77 | // This will prevent compacting in the first IN_DRAW_COMPACT_READINGS frames. 78 | foreach(i; 0..prevIndiceCount.length) { 79 | prevIndiceCount[i] = size_t.max; 80 | } 81 | } 82 | 83 | 84 | /** 85 | Gets current draw commands in draw list. 86 | 87 | This should not be written to. 88 | */ 89 | DrawCommand[] getDrawCommands() { 90 | return commands[0..$]; 91 | } 92 | 93 | /** 94 | Gets current vertices in draw list 95 | 96 | This should not be written to. 97 | */ 98 | DrawVertex[] getVertexBuffer() { 99 | return vertices[0..$]; 100 | } 101 | 102 | /** 103 | Gets current vertices in draw list 104 | 105 | This should not be written to. 106 | */ 107 | uint[] getIndexBuffer() { 108 | return indices[0..$]; 109 | } 110 | 111 | /** 112 | Compacts the buffers 113 | */ 114 | void compact() { 115 | commands.shrinkToFit(); 116 | vertices.shrinkToFit(); 117 | indices.shrinkToFit(); 118 | } 119 | 120 | /** 121 | Begins a render pass. 122 | 123 | This is automatically called by the puppet when 124 | drawing is invoked for it. 125 | */ 126 | void begin() { 127 | 128 | // Compacting step 129 | { 130 | // Determine how many times in the last few frames that the size 131 | // of the draw list was lower than it is currently. 132 | size_t lowerCount = 0; 133 | foreach(i; 0..IN_DRAW_COMPACT_READINGS-1) 134 | if (prevIndiceCount[i] < prevIndiceCount[IN_DRAW_COMPACT_READINGS]) 135 | lowerCount++; 136 | 137 | // If below the compile-time set threshold, compact the memory to save space. 138 | if (lowerCount >= IN_DRAW_COMPACT_THRESHOLD) 139 | this.compact(); 140 | } 141 | 142 | // Setup step. 143 | { 144 | // NOTE: numem vectors will not free memory when length is changed 145 | // without calls to shrinkToFit. This allows reusing the memory 146 | // using the append operator, which is safer. 147 | commands.resize(0); 148 | vertices.resize(0); 149 | indices.resize(0); 150 | } 151 | } 152 | 153 | /** 154 | Ends a render pass 155 | */ 156 | void end() { 157 | // Move all indices one step back 158 | prevIndiceCount[0..IN_DRAW_COMPACT_READINGS] = prevIndiceCount[1..IN_DRAW_COMPACT_READINGS+1]; 159 | prevIndiceCount[IN_DRAW_COMPACT_READINGS] = indices.size(); 160 | } 161 | 162 | /** 163 | Blits a source framebuffer to the targeted framebuffer. 164 | 165 | Remember to finalize the draw submission with the 166 | submit(DrawCommand) call. 167 | */ 168 | DrawCommand blit(InResourceID source, InResourceID target) { 169 | DrawCommand ret; 170 | ret.type = CommandType.Blit; 171 | ret.source = source; 172 | ret.target = target; 173 | 174 | return ret; 175 | } 176 | 177 | /** 178 | Adds vertices and indices into the draw list and returns 179 | a draw command ready to be filled out with supplementary 180 | render information. 181 | 182 | Remember to finalize the draw submission with the 183 | submit(DrawCommand) call. 184 | */ 185 | DrawCommand draw(DrawVertex[] vertices, uint[] indices) { 186 | DrawCommand ret; 187 | 188 | // Get offset to beginning of data. 189 | uint vtxOffset = cast(uint)this.vertices.size(); 190 | uint idxOffset = cast(uint)this.indices.size(); 191 | 192 | this.vertices ~= vertices; 193 | this.indices ~= indices; 194 | 195 | ret.vtxOffset = vtxOffset; 196 | ret.idxOffset = idxOffset; 197 | ret.drawCount = cast(uint)indices.length; 198 | 199 | return ret; 200 | } 201 | 202 | /** 203 | Submit a command to the list, 204 | */ 205 | void submit(DrawCommand command) { 206 | 207 | // Error condition handling. 208 | switch(command.type) { 209 | 210 | case CommandType.Draw: 211 | // Don't allow invalid render commands. 212 | if (command.vtxOffset >= this.vertices.size()) return; 213 | if (command.idxOffset >= this.indices.size()) return; 214 | if (command.idxOffset + command.drawCount > this.indices.size()) return; 215 | if (command.source[0] is null) return; 216 | break; 217 | 218 | default: 219 | break; 220 | } 221 | 222 | commands ~= command; 223 | } 224 | } -------------------------------------------------------------------------------- /source/inochi2d/core/draw/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.draw; 9 | 10 | import inochi2d.core.draw.blending; 11 | import inochi2d.core.draw.cmd; 12 | import inochi2d.core.draw.list; 13 | import inochi2d.core.draw.render; -------------------------------------------------------------------------------- /source/inochi2d/core/draw/render.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.draw.render; 9 | import inochi2d.core.draw.list; 10 | 11 | /** 12 | Texture reference 13 | */ 14 | alias InResourceID = void*; 15 | 16 | /** 17 | Functions which should be implemented by an Inochi2D renderer. 18 | */ 19 | struct RendererFunctions { 20 | @nogc: 21 | public extern(C): 22 | 23 | /** 24 | Called by the renderer during initialization. 25 | 26 | Should return the userData to be passed to subsequent function calls. 27 | The userData should be freed in shutdown. 28 | */ 29 | void* function() initialize; 30 | 31 | /** 32 | Called by the renderer during shutdown. 33 | 34 | Remember to free the userData! 35 | */ 36 | void function(void* userData) shutdown; 37 | 38 | /** 39 | Creates a texture 40 | */ 41 | InResourceID function (void* userData, size_t width, size_t height) createTexture; 42 | 43 | /** 44 | Uploads data to texture 45 | */ 46 | bool function(void* userData, InResourceID texture, ubyte* data, uint length) uploadTextureData; 47 | 48 | /** 49 | Destroys a texture 50 | */ 51 | bool function (void* userData, InResourceID texture) destroyTexture; 52 | 53 | /** 54 | Creates a framebuffer 55 | */ 56 | InResourceID function(void* userData, size_t width, size_t height) createFramebuffer; 57 | 58 | /** 59 | Destroys a framebuffer 60 | */ 61 | bool function(void* userData, InResourceID fb) destroyFramebuffer; 62 | 63 | /** 64 | Called when a drawlist is requesting to be rendered. 65 | */ 66 | bool function (void* userData, DrawList* drawList) renderList; 67 | } 68 | 69 | /** 70 | Inochi2D renderer interface. 71 | */ 72 | class Renderer { 73 | @nogc: 74 | private: 75 | RendererFunctions renderFuncs; 76 | void* userData; 77 | 78 | public: 79 | 80 | /** 81 | Destructor 82 | */ 83 | ~this() { 84 | this.renderFuncs.shutdown(userData); 85 | } 86 | 87 | /** 88 | Creates a new renderer with the specified rendering functions. 89 | */ 90 | this(RendererFunctions funcs) { 91 | this.renderFuncs = funcs; 92 | this.userData = funcs.initialize(); 93 | } 94 | 95 | /** 96 | Creates a texture 97 | */ 98 | InResourceID createTexture(size_t width, size_t height) { 99 | return this.renderFuncs.createTexture(userData, width, height); 100 | } 101 | 102 | /** 103 | Uploads data to texture 104 | */ 105 | bool uploadTextureData(InResourceID texture, ubyte* data, uint length) { 106 | return this.renderFuncs.uploadTextureData(userData, texture, data, length); 107 | } 108 | 109 | /** 110 | Destroys a texture 111 | */ 112 | bool destroyTexture(InResourceID texture) { 113 | return this.renderFuncs.destroyTexture(userData, texture); 114 | } 115 | 116 | /** 117 | Creates a framebuffer 118 | */ 119 | InResourceID createFramebuffer(size_t width, size_t height) { 120 | return this.renderFuncs.createFramebuffer(userData, width, height); 121 | } 122 | 123 | /** 124 | Destroys a framebuffer 125 | */ 126 | bool destroyFramebuffer(InResourceID fb) { 127 | return this.renderFuncs.destroyFramebuffer(userData, fb); 128 | } 129 | 130 | /** 131 | Called when a drawlist is requesting to be rendered. 132 | */ 133 | bool renderList (DrawList* drawList) { 134 | return this.renderFuncs.renderList(userData, drawList); 135 | } 136 | } -------------------------------------------------------------------------------- /source/inochi2d/core/expr/compiler/builder.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.expr.compiler.builder; 9 | import inochi2d.core.expr.vm.opcodes; 10 | import inochi2d.core.expr.vm.stack; 11 | import numem.all; 12 | 13 | /** 14 | A builder which can emit Inochi2D Expression instructions. 15 | */ 16 | final 17 | class InVmBytecodeBuilder { 18 | @nogc: 19 | private: 20 | vector!ubyte bytecode; 21 | 22 | map!(nstring, size_t) labels; 23 | 24 | ptrdiff_t eStackPtr; 25 | bool underflow; 26 | bool overflow; 27 | 28 | // Validates the code every stack step. 29 | void validateStack() { 30 | if (eStackPtr < 0) underflow = true; 31 | if (eStackPtr >= INVM_STACK_SIZE) overflow = true; 32 | } 33 | 34 | public: 35 | ~this() { 36 | nogc_delete(labels); 37 | } 38 | 39 | this() { 40 | this.eStackPtr = 0; 41 | } 42 | 43 | /** 44 | Adds a label at the current instruction 45 | */ 46 | void setLabel(nstring label) { 47 | labels[label] = bytecode.size()-1; 48 | } 49 | 50 | /** 51 | Adds a label at the current instruction 52 | */ 53 | void setLabel(string label) { 54 | this.setLabel(nstring(label)); 55 | } 56 | 57 | /** 58 | Gets the bytecode position of a label 59 | */ 60 | ptrdiff_t getLabel(nstring label) { 61 | if (label in labels) { 62 | return labels[label]; 63 | } 64 | 65 | return -1; 66 | } 67 | 68 | /** 69 | Gets the bytecode position of a label 70 | */ 71 | ptrdiff_t getLabel(string label) { 72 | return this.getLabel(nstring(label)); 73 | } 74 | 75 | /** 76 | Builds a NOP instruction 77 | */ 78 | void buildNOP() { 79 | bytecode ~= InVmOpCode.NOP; 80 | } 81 | 82 | /** 83 | Builds a ADD instruction 84 | */ 85 | void buildADD() { 86 | bytecode ~= InVmOpCode.ADD; 87 | eStackPtr--; 88 | this.validateStack(); 89 | } 90 | 91 | /** 92 | Builds a SUB instruction 93 | */ 94 | void buildSUB() { 95 | bytecode ~= InVmOpCode.SUB; 96 | eStackPtr--; 97 | this.validateStack(); 98 | } 99 | 100 | /** 101 | Builds a DIV instruction 102 | */ 103 | void buildDIV() { 104 | bytecode ~= InVmOpCode.DIV; 105 | eStackPtr--; 106 | this.validateStack(); 107 | } 108 | 109 | /** 110 | Builds a MUL instruction 111 | */ 112 | void buildMUL() { 113 | bytecode ~= InVmOpCode.MUL; 114 | eStackPtr--; 115 | this.validateStack(); 116 | } 117 | 118 | /** 119 | Builds a MUL instruction 120 | */ 121 | void buildMOD() { 122 | bytecode ~= InVmOpCode.MOD; 123 | eStackPtr--; 124 | this.validateStack(); 125 | } 126 | 127 | /** 128 | Builds a NEG instruction 129 | */ 130 | void buildNEG() { 131 | bytecode ~= InVmOpCode.NEG; 132 | } 133 | 134 | /** 135 | Builds a PUSH instruction 136 | */ 137 | void buildPUSH(float immediate) { 138 | bytecode ~= InVmOpCode.PUSH_n; 139 | bytecode ~= toEndian(immediate, Endianess.littleEndian); 140 | eStackPtr++; 141 | this.validateStack(); 142 | } 143 | 144 | /** 145 | Builds a PUSH instruction 146 | */ 147 | void buildPUSH(string str) { 148 | this.buildPUSH(nstring(str)); 149 | } 150 | 151 | /** 152 | Builds a PUSH instruction 153 | */ 154 | void buildPUSH(nstring str) { 155 | bytecode ~= InVmOpCode.PUSH_s; 156 | bytecode ~= toEndian(cast(uint)str.length(), Endianess.littleEndian); 157 | bytecode ~= cast(ubyte[])str[0..$]; 158 | eStackPtr++; 159 | this.validateStack(); 160 | } 161 | 162 | /** 163 | Builds a POP instruction 164 | */ 165 | void buildPOP(uint count) { 166 | this.buildPOP(0, count); 167 | } 168 | 169 | /** 170 | Builds a POP instruction 171 | */ 172 | void buildPOP(uint offset, uint count) { 173 | bytecode ~= InVmOpCode.POP; 174 | bytecode ~= cast(ubyte)offset; 175 | bytecode ~= cast(ubyte)count; 176 | 177 | eStackPtr -= count; 178 | this.validateStack(); 179 | } 180 | 181 | /** 182 | Builds a PEEK instruction 183 | */ 184 | void buildPEEK(uint offset) { 185 | bytecode ~= InVmOpCode.PEEK; 186 | bytecode ~= cast(ubyte)offset; 187 | 188 | eStackPtr++; 189 | this.validateStack(); 190 | } 191 | 192 | /** 193 | Builds a CMP instruction 194 | */ 195 | void buildCMP(uint offsetA, uint offsetB) { 196 | bytecode ~= InVmOpCode.CMP; 197 | bytecode ~= cast(ubyte)offsetA; 198 | bytecode ~= cast(ubyte)offsetB; 199 | 200 | eStackPtr++; 201 | this.validateStack(); 202 | } 203 | 204 | /** 205 | Builds a JMP instruction 206 | */ 207 | void buildJMP(size_t offset) { 208 | bytecode ~= InVmOpCode.JMP; 209 | bytecode ~= toEndian(offset, Endianess.littleEndian); 210 | } 211 | 212 | /** 213 | Builds a JEQ instruction 214 | */ 215 | void buildJEQ(size_t offset) { 216 | bytecode ~= InVmOpCode.JEQ; 217 | bytecode ~= toEndian(offset, Endianess.littleEndian); 218 | 219 | eStackPtr--; 220 | this.validateStack(); 221 | } 222 | 223 | /** 224 | Builds a JEQ instruction 225 | */ 226 | void buildJNQ(size_t offset) { 227 | bytecode ~= InVmOpCode.JEQ; 228 | bytecode ~= toEndian(offset, Endianess.littleEndian); 229 | 230 | eStackPtr--; 231 | this.validateStack(); 232 | } 233 | 234 | /** 235 | Builds a JL instruction 236 | */ 237 | void buildJL(size_t offset) { 238 | bytecode ~= InVmOpCode.JL; 239 | bytecode ~= toEndian(offset, Endianess.littleEndian); 240 | 241 | eStackPtr--; 242 | this.validateStack(); 243 | } 244 | 245 | /** 246 | Builds a JG instruction 247 | */ 248 | void buildJG(size_t offset) { 249 | bytecode ~= InVmOpCode.JG; 250 | bytecode ~= toEndian(offset, Endianess.littleEndian); 251 | 252 | eStackPtr--; 253 | this.validateStack(); 254 | } 255 | 256 | /** 257 | Builds a JSR instruction 258 | */ 259 | void buildJSR() { 260 | bytecode ~= InVmOpCode.JSR; 261 | } 262 | 263 | /** 264 | Builds a RET instruction 265 | */ 266 | void buildRET() { 267 | bytecode ~= InVmOpCode.RET; 268 | } 269 | 270 | /** 271 | Builds a SETG instruction 272 | */ 273 | void buildSETG() { 274 | bytecode ~= InVmOpCode.SETG; 275 | 276 | eStackPtr -= 2; 277 | this.validateStack(); 278 | } 279 | 280 | /** 281 | Builds a GETG instruction 282 | */ 283 | void buildGETG() { 284 | bytecode ~= InVmOpCode.GETG; 285 | } 286 | 287 | /** 288 | Whether code underflows stack 289 | */ 290 | bool hasUnderflow() { 291 | return underflow; 292 | } 293 | 294 | /** 295 | Whether code overflows stack 296 | */ 297 | bool hasOverflow() { 298 | return overflow; 299 | } 300 | 301 | /** 302 | Finalizes the bytecode 303 | */ 304 | vector!ubyte finalize() { 305 | 306 | // Padding NOP needed. 307 | this.buildNOP(); 308 | vector!ubyte finalized = vector!ubyte(bytecode); 309 | 310 | bytecode.resize(0); 311 | eStackPtr = 0; 312 | underflow = false; 313 | overflow = false; 314 | 315 | return finalized; 316 | } 317 | } -------------------------------------------------------------------------------- /source/inochi2d/core/expr/compiler/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Inochi2D Expression Compiler 10 | 11 | This is the compiler for the Inochi2D Expression language. 12 | */ 13 | module inochi2d.core.expr.compiler; 14 | 15 | 16 | public import inochi2d.core.expr.compiler.builder; -------------------------------------------------------------------------------- /source/inochi2d/core/expr/vm/opcodes.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.expr.vm.opcodes; 9 | import numem.all; 10 | 11 | enum InVmOpCode : ubyte { 12 | 13 | /// No-op, placeholder opcode. 14 | NOP = 0x00, 15 | 16 | // 17 | // MATH OPCODES 18 | // 19 | 20 | /// Add top 2 values of stack 21 | ADD = 0x01, 22 | 23 | /// Subtract top 2 values of stack 24 | SUB = 0x02, 25 | 26 | /// Divide top 2 values of stack 27 | DIV = 0x03, 28 | 29 | /// Multiply top 2 values of stack 30 | MUL = 0x04, 31 | 32 | /// Modulo top 2 values of stack 33 | MOD = 0x05, 34 | 35 | /// Negate value 36 | NEG = 0x06, 37 | 38 | 39 | 40 | // 41 | // STACK OPCODES 42 | // 43 | 44 | // Push number to stack 45 | PUSH_n = 0x10, 46 | 47 | // Push string to stack 48 | PUSH_s = 0x13, 49 | 50 | /// Pop value range off stack 51 | POP = 0x1A, 52 | 53 | /// Peek value on stack 54 | PEEK = 0x1B, 55 | 56 | 57 | // 58 | // JUMP OPCODES 59 | // 60 | 61 | /// Compare top 2 values of stack 62 | CMP = 0x20, 63 | 64 | /// Unconditional jump 65 | JMP = 0x21, 66 | 67 | /// Jump if equal 68 | JEQ = 0x22, 69 | 70 | /// Jump if equal 71 | JNQ = 0x23, 72 | 73 | /// Jump if less-than 74 | JL = 0x24, 75 | 76 | /// Jump if less-than or equal 77 | JLE = 0x25, 78 | 79 | /// Jump if greater-than 80 | JG = 0x26, 81 | 82 | /// Jump if greater-than or equal 83 | JGE = 0x27, 84 | 85 | /// Jump to subroutine 86 | JSR = 0x28, 87 | 88 | /// Return from subroutine 89 | RET = 0x29, 90 | 91 | // 92 | // HEAP OPCODES 93 | // 94 | 95 | /// Set global 96 | SETG = 0x30, 97 | 98 | /// Get global 99 | GETG = 0x31, 100 | } -------------------------------------------------------------------------------- /source/inochi2d/core/expr/vm/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Inochi2D Expression Virtual Machine 10 | 11 | This is a small virtual machine (programming language) for executing 12 | mathematical expressions within the context of Inochi2D. 13 | 14 | The VM seperates call and value stacks and strictly limits which 15 | types may be used in conjunction with it for security. 16 | 17 | The VM is stack based and does not use a JIT, for maximum compatibility 18 | with platforms which do not support the use of JIT compilation. 19 | */ 20 | module inochi2d.core.expr.vm; 21 | 22 | public import inochi2d.core.expr.vm.vm; 23 | public import inochi2d.core.expr.vm.value; 24 | public import inochi2d.core.expr.vm.stack; -------------------------------------------------------------------------------- /source/inochi2d/core/expr/vm/stack.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | module inochi2d.core.expr.vm.stack; 8 | import inochi2d.core.expr.vm.value; 9 | import numem.all; 10 | 11 | /** 12 | Size of a VM stack. 13 | */ 14 | enum INVM_STACK_SIZE = 64; 15 | 16 | /** 17 | A stack-frame, normally not user accessible. 18 | */ 19 | struct InVmFrame { 20 | 21 | /** 22 | Whether the frame is within the VM. 23 | */ 24 | bool isWithinVM; 25 | 26 | /** 27 | Program 28 | */ 29 | ubyte[] prog; 30 | 31 | /** 32 | Program Counter 33 | */ 34 | uint pc; 35 | } 36 | 37 | /** 38 | A VM stack 39 | */ 40 | final 41 | class InVmStack(T) { 42 | @nogc: 43 | private: 44 | size_t sp = 0; 45 | T[INVM_STACK_SIZE] stack; 46 | 47 | public: 48 | 49 | ~this() { 50 | foreach(i; 0..INVM_STACK_SIZE) { 51 | nogc_delete(stack[i]); 52 | } 53 | } 54 | 55 | /** 56 | Pushes an item to the stack 57 | */ 58 | void push(T item) { 59 | if (sp+1 >= INVM_STACK_SIZE) return; 60 | stack[sp++] = item; 61 | } 62 | 63 | /** 64 | Inserts value at offset. 65 | */ 66 | void insert(T val, ptrdiff_t offset) { 67 | ptrdiff_t rsp = cast(ptrdiff_t)sp; 68 | if (rsp+1 >= INVM_STACK_SIZE) return; 69 | 70 | // Insert end 71 | if (offset == 0) { 72 | this.push(val); 73 | return; 74 | } 75 | 76 | // Snap to bottom 77 | if (rsp - offset <= 0) { 78 | stack[1..sp+1] = stack[0..sp]; 79 | stack[0] = val; 80 | sp++; 81 | return; 82 | } 83 | 84 | // Insert middle 85 | ptrdiff_t start = rsp-offset; 86 | stack[start+1..rsp+1] = stack[start..rsp]; 87 | stack[start] = val; 88 | } 89 | 90 | /** 91 | Pops a range of values off the stack 92 | */ 93 | void pop(ptrdiff_t offset, ptrdiff_t count) { 94 | ptrdiff_t rsp = cast(ptrdiff_t)sp; 95 | 96 | if (offset == 0) { 97 | this.pop(count); 98 | } else if (offset > 0) { 99 | 100 | ptrdiff_t start = rsp-offset; 101 | ptrdiff_t end = start-count; 102 | 103 | ptrdiff_t toCopy = rsp-start; 104 | 105 | if (end < 0) return; 106 | if (start > rsp) return; 107 | if (toCopy > 0) { 108 | 109 | // Be good citizens and do some housekeeping. 110 | foreach(i; end..end+toCopy) { 111 | nogc_delete(stack[i]); 112 | } 113 | 114 | stack[end..end+toCopy] = stack[start..start+toCopy]; 115 | sp -= count; 116 | } 117 | } 118 | } 119 | 120 | /** 121 | Pops a range of values off the stack 122 | */ 123 | void pop(ptrdiff_t count) { 124 | ptrdiff_t rsp = cast(ptrdiff_t)sp; 125 | if (rsp-count < 0) return; 126 | sp -= count; 127 | } 128 | 129 | /** 130 | Pops the top value off the stack 131 | */ 132 | T* pop() { 133 | ptrdiff_t rsp = cast(ptrdiff_t)sp; 134 | if (rsp-1 < 0) return null; 135 | return &stack[--sp]; 136 | } 137 | 138 | 139 | /** 140 | Gets the value at a specific offset. 141 | Returns null if peeking below 0 or above the stack pointer. 142 | */ 143 | T* peek(ptrdiff_t offset) { 144 | ptrdiff_t rsp = cast(ptrdiff_t)sp-1; 145 | 146 | if (rsp-offset < 0) return null; 147 | if (rsp-offset > rsp) return null; 148 | 149 | return &stack[rsp-offset]; 150 | } 151 | 152 | /** 153 | Gets the stack depth 154 | */ 155 | size_t getDepth() { 156 | return sp; 157 | } 158 | 159 | /** 160 | Gets the maximum depth of the stack 161 | */ 162 | size_t getMaxDepth() { 163 | return INVM_STACK_SIZE; 164 | } 165 | } 166 | 167 | alias InVmValueStack = InVmStack!InVmValue; 168 | alias InVmCallStack = InVmStack!InVmFrame; -------------------------------------------------------------------------------- /source/inochi2d/core/expr/vm/value.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | module inochi2d.core.expr.vm.value; 8 | import inochi2d.core.expr.vm.stack; 9 | import inochi2d.core.expr.vm.vm; 10 | import numem.all; 11 | 12 | enum InVmValueType { 13 | NONE, 14 | number, 15 | str, 16 | bytecode, 17 | nativeFunction, 18 | returnAddr 19 | } 20 | 21 | struct InVmValue { 22 | @nogc: 23 | private: 24 | InVmValueType type; 25 | 26 | public: 27 | union { 28 | /// number 29 | float number; 30 | 31 | /// string 32 | nstring str; 33 | 34 | /// Executable bytecode 35 | vector!ubyte bytecode; 36 | 37 | /// Native function 38 | int function(ref InVmState state) func; 39 | } 40 | 41 | /** 42 | Destructor 43 | */ 44 | ~this() { 45 | if (type == InVmValueType.str) { 46 | if (!str.empty) 47 | nogc_delete(str); 48 | } 49 | } 50 | 51 | /** 52 | Constructor 53 | */ 54 | this(float f32) { 55 | type = InVmValueType.number; 56 | this.number = f32; 57 | } 58 | 59 | /** 60 | Constructor 61 | */ 62 | this(nstring str) { 63 | type = InVmValueType.str; 64 | this.str = str; 65 | } 66 | 67 | /** 68 | Constructor 69 | */ 70 | this(vector!ubyte bytecode) { 71 | type = InVmValueType.bytecode; 72 | this.bytecode = bytecode; 73 | } 74 | 75 | /** 76 | Constructor 77 | */ 78 | this(int function(ref InVmState stack) @nogc func) { 79 | type = InVmValueType.nativeFunction; 80 | this.func = func; 81 | } 82 | 83 | /** 84 | Get whether the value callable 85 | */ 86 | bool isCallable() { 87 | return type == InVmValueType.nativeFunction || type == InVmValueType.bytecode; 88 | } 89 | 90 | /** 91 | Get whether the value is a native function 92 | */ 93 | bool isNativeFunction() { 94 | return type == InVmValueType.nativeFunction; 95 | } 96 | 97 | /** 98 | Get whether the value is a bytecode blob 99 | */ 100 | bool isBytecodeBlob() { 101 | return type == InVmValueType.bytecode; 102 | } 103 | 104 | /** 105 | Get whether 2 values are compatible 106 | */ 107 | bool isCompatible(ref InVmValue other) { 108 | final switch(this.getType()) { 109 | case InVmValueType.NONE: 110 | return false; 111 | case InVmValueType.number: 112 | case InVmValueType.str: 113 | case InVmValueType.nativeFunction: 114 | case InVmValueType.bytecode: 115 | case InVmValueType.returnAddr: 116 | return other.getType() == this.getType(); 117 | } 118 | } 119 | 120 | /** 121 | Gets whether the value is numeric 122 | */ 123 | bool isNumeric() { 124 | return type == InVmValueType.number; 125 | } 126 | 127 | /** 128 | Gets the type of the value 129 | */ 130 | InVmValueType getType() { 131 | return type; 132 | } 133 | } -------------------------------------------------------------------------------- /source/inochi2d/core/expr/vm/vm.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.expr.vm.vm; 9 | import inochi2d.core.expr.vm.value; 10 | import inochi2d.core.expr.vm.opcodes; 11 | import inochi2d.core.expr.vm.stack; 12 | import numem.all; 13 | import std.string; 14 | 15 | private { 16 | 17 | // Vm execution state 18 | struct _vmExecutionState { 19 | @nogc: 20 | 21 | // Value Stack 22 | InVmValueStack stack; 23 | 24 | // Call stack 25 | InVmCallStack callStack; 26 | 27 | /// Bytecode being executed 28 | ubyte[] bc; 29 | 30 | /// Program counter 31 | uint pc; 32 | 33 | /// Current operation flag 34 | ubyte flags; 35 | 36 | /// Get if the previous CMP flag was set to Equal 37 | bool flagEq() { 38 | return (flags & InVmFlag.eq) != 0; 39 | } 40 | 41 | /// Get if the previous CMP flag was set to Below 42 | bool flagBelow() { 43 | return (flags & InVmFlag.below) != 0; 44 | } 45 | 46 | /// Get if the previous CMP flag was set to Above 47 | bool flagAbove() { 48 | return flags == 0; 49 | } 50 | } 51 | 52 | // Vm execution state flags 53 | enum InVmFlag : ubyte { 54 | /// Equal flag (zero flag) 55 | eq = 0b0000_0001, 56 | 57 | /// Below flag (carry flag) 58 | below = 0b0000_0010, 59 | 60 | /// Invalid operation flag. 61 | invalidOp = 0b0001_0000, 62 | } 63 | 64 | // An itme in the global store 65 | struct _vmGlobalItem { 66 | bool writeProtected; 67 | InVmValue value; 68 | } 69 | } 70 | 71 | /** 72 | A execution environment 73 | */ 74 | abstract 75 | class InVmState { 76 | @nogc: 77 | private: 78 | _vmExecutionState state; 79 | 80 | void _vmcmp(T)(T lhs, T rhs) { 81 | state.flags = 0; 82 | 83 | if (lhs == rhs) state.flags |= InVmFlag.eq; 84 | if (lhs < rhs) state.flags |= InVmFlag.below; 85 | } 86 | 87 | struct _vmGlobalState { 88 | @nogc: 89 | map!(nstring, _vmGlobalItem) globals; 90 | 91 | bool set(bool host=false)(nstring name, InVmValue value, bool writeProtect=false) { 92 | 93 | // Handle write protection. 94 | static if (!host) { 95 | if (name in globals) { 96 | if (globals[name].writeProtected) 97 | return false; 98 | } 99 | } 100 | 101 | globals[name] = _vmGlobalItem( 102 | host && writeProtect, 103 | value 104 | ); 105 | 106 | return true; 107 | } 108 | 109 | InVmValue* get(nstring name) { 110 | if (name in globals) { 111 | return &globals[name].value; 112 | } 113 | return null; 114 | } 115 | } 116 | 117 | bool _vcall(InVmValue* func) { 118 | 119 | // Get information 120 | if (!func || !func.isCallable()) return false; 121 | 122 | // Store stack frame 123 | InVmFrame frame; 124 | if (state.callStack.getDepth() == 0) { 125 | frame.isWithinVM = false; 126 | } else { 127 | frame.isWithinVM = true; 128 | frame.prog = state.bc; 129 | frame.pc = state.pc; 130 | } 131 | state.callStack.push(frame); 132 | 133 | // Call function. 134 | if (func.isNativeFunction()) { 135 | func.func(this); 136 | 137 | } else { 138 | 139 | this.state.pc = 0; 140 | this.state.bc = func.bytecode[]; 141 | } 142 | 143 | return true; 144 | } 145 | 146 | bool _vreturn() { 147 | ptrdiff_t stackDepth = cast(ptrdiff_t)state.callStack.getDepth(); 148 | 149 | // Return to caller 150 | InVmFrame* frame = state.callStack.pop(); 151 | 152 | // No frame? 153 | if (!frame) return false; 154 | 155 | // NOTE: Native functions may end up calling back into the VM. 156 | // This value should be set by JSR when calling into a native function. 157 | // which tells it to return to caller. 158 | if (!frame.isWithinVM) return false; 159 | 160 | // Restore previous frame 161 | this.state.pc = frame.pc; 162 | this.state.bc = frame.prog; 163 | return true; 164 | } 165 | 166 | protected: 167 | 168 | /** 169 | Gets the local execution state 170 | */ 171 | final 172 | _vmExecutionState getState() { 173 | return state; 174 | } 175 | 176 | /** 177 | Gets the global execution state 178 | */ 179 | abstract _vmGlobalState* getGlobalState(); 180 | 181 | /** 182 | Jumps to the specified address. 183 | */ 184 | final 185 | void jump(size_t offset) { 186 | if (offset >= state.pc) return; 187 | state.pc = cast(uint)offset; 188 | } 189 | 190 | /** 191 | Runs a single instruction 192 | */ 193 | final 194 | bool runOne() { 195 | import core.stdc.stdio : printf; 196 | InVmOpCode opcode = cast(InVmOpCode)state.bc[state.pc++]; 197 | 198 | switch(opcode) { 199 | default: 200 | return false; 201 | 202 | case InVmOpCode.NOP: 203 | return true; 204 | 205 | case InVmOpCode.ADD: 206 | InVmValue* rhsp = state.stack.peek(0); 207 | InVmValue* lhsp = state.stack.peek(1); 208 | if (!rhsp || !lhsp) 209 | return false; 210 | 211 | InVmValue rhs = *rhsp; 212 | InVmValue lhs = *lhsp; 213 | 214 | if (lhs.isNumeric && rhs.isNumeric) { 215 | state.stack.pop(2); 216 | state.stack.push(InVmValue(lhs.number + rhs.number)); 217 | } 218 | return false; 219 | 220 | case InVmOpCode.SUB: 221 | InVmValue* rhsp = state.stack.peek(0); 222 | InVmValue* lhsp = state.stack.peek(1); 223 | if (!rhsp || !lhsp) 224 | return false; 225 | 226 | InVmValue rhs = *rhsp; 227 | InVmValue lhs = *lhsp; 228 | 229 | if (lhs.isNumeric && rhs.isNumeric) { 230 | state.stack.pop(2); 231 | state.stack.push(InVmValue(lhs.number - rhs.number)); 232 | } 233 | return false; 234 | 235 | case InVmOpCode.MUL: 236 | InVmValue* rhsp = state.stack.peek(0); 237 | InVmValue* lhsp = state.stack.peek(1); 238 | if (!rhsp || !lhsp) 239 | return false; 240 | 241 | InVmValue rhs = *rhsp; 242 | InVmValue lhs = *lhsp; 243 | 244 | if (lhs.isNumeric && rhs.isNumeric) { 245 | state.stack.pop(2); 246 | state.stack.push(InVmValue(lhs.number * rhs.number)); 247 | } 248 | return false; 249 | 250 | case InVmOpCode.DIV: 251 | InVmValue* rhsp = state.stack.peek(0); 252 | InVmValue* lhsp = state.stack.peek(1); 253 | if (!rhsp || !lhsp) 254 | return false; 255 | 256 | InVmValue rhs = *rhsp; 257 | InVmValue lhs = *lhsp; 258 | 259 | if (lhs.isNumeric && rhs.isNumeric) { 260 | state.stack.pop(2); 261 | state.stack.push(InVmValue(lhs.number / rhs.number)); 262 | } 263 | return false; 264 | 265 | case InVmOpCode.MOD: 266 | InVmValue* rhsp = state.stack.peek(0); 267 | InVmValue* lhsp = state.stack.peek(1); 268 | if (!rhsp || !lhsp) 269 | return false; 270 | 271 | InVmValue rhs = *rhsp; 272 | InVmValue lhs = *lhsp; 273 | 274 | if (lhs.isNumeric && rhs.isNumeric) { 275 | import inmath.math : fmodf; 276 | state.stack.pop(2); 277 | state.stack.push(InVmValue(fmodf(lhs.number, rhs.number))); 278 | } 279 | return false; 280 | 281 | case InVmOpCode.NEG: 282 | InVmValue* lhsp = state.stack.peek(0); 283 | if (!lhsp) 284 | return false; 285 | 286 | InVmValue lhs = *lhsp; 287 | 288 | if (lhs.isNumeric) { 289 | state.stack.pop(1); 290 | state.stack.push(InVmValue(-lhs.number)); 291 | } 292 | return false; 293 | 294 | case InVmOpCode.PUSH_n: 295 | ubyte[4] val = state.bc[state.pc..state.pc+4]; 296 | float f32 = fromEndian!float(val, Endianess.littleEndian); 297 | state.stack.push(InVmValue(f32)); 298 | state.pc += 4; 299 | return true; 300 | 301 | case InVmOpCode.PUSH_s: 302 | ubyte[4] val = state.bc[state.pc..state.pc+4]; 303 | state.pc += 4; 304 | 305 | uint length = fromEndian!uint(val, Endianess.littleEndian); 306 | 307 | // Invalid string. 308 | if (state.pc+length >= state.bc.length) 309 | return false; 310 | 311 | nstring nstr = cast(string)state.bc[state.pc..state.pc+length]; 312 | state.stack.push(InVmValue(nstr)); 313 | 314 | state.pc += length; 315 | return true; 316 | 317 | case InVmOpCode.POP: 318 | ptrdiff_t offset = state.bc[state.pc++]; 319 | ptrdiff_t count = state.bc[state.pc++]; 320 | state.stack.pop(offset, count); 321 | return true; 322 | 323 | case InVmOpCode.PEEK: 324 | ptrdiff_t offset = state.bc[state.pc++]; 325 | state.stack.push(*state.stack.peek(offset)); 326 | return true; 327 | 328 | case InVmOpCode.CMP: 329 | state.flags = InVmFlag.invalidOp; 330 | 331 | InVmValue* rhs = state.stack.peek(0); 332 | InVmValue* lhs = state.stack.peek(1); 333 | 334 | if (lhs.isNumeric && rhs.isNumeric) { 335 | _vmcmp(lhs.number, rhs.number); 336 | } 337 | return false; 338 | 339 | case InVmOpCode.JMP: 340 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 341 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 342 | state.pc += 4; 343 | 344 | this.jump(addr); 345 | return true; 346 | 347 | case InVmOpCode.JEQ: 348 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 349 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 350 | state.pc += 4; 351 | 352 | if (state.flagEq()) 353 | this.jump(addr); 354 | return true; 355 | 356 | case InVmOpCode.JNQ: 357 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 358 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 359 | state.pc += 4; 360 | 361 | if (!state.flagEq()) 362 | this.jump(addr); 363 | return true; 364 | 365 | case InVmOpCode.JL: 366 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 367 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 368 | state.pc += 4; 369 | 370 | if (state.flagBelow()) 371 | this.jump(addr); 372 | return true; 373 | 374 | case InVmOpCode.JLE: 375 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 376 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 377 | state.pc += 4; 378 | 379 | if (state.flagBelow() || state.flagEq()) 380 | this.jump(addr); 381 | return true; 382 | 383 | case InVmOpCode.JG: 384 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 385 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 386 | state.pc += 4; 387 | 388 | if (state.flagAbove()) 389 | this.jump(addr); 390 | return true; 391 | 392 | case InVmOpCode.JGE: 393 | ubyte[4] var = state.bc[state.pc..state.pc+4]; 394 | uint addr = fromEndian!uint(var, Endianess.littleEndian); 395 | state.pc += 4; 396 | 397 | if (state.flagAbove() || state.flagEq()) 398 | this.jump(addr); 399 | return true; 400 | 401 | case InVmOpCode.JSR: 402 | 403 | // Get information 404 | InVmValue* func = state.stack.pop(); 405 | return this._vcall(func); 406 | 407 | case InVmOpCode.RET: 408 | return this._vreturn(); 409 | 410 | case InVmOpCode.SETG: 411 | InVmValue* name = state.stack.pop(); 412 | InVmValue* item = state.stack.pop(); 413 | 414 | if (name && item && name.getType() == InVmValueType.str) { 415 | state.stack.pop(2); 416 | 417 | // TODO: emit error if write protected. 418 | return this.getGlobalState().set(name.str, *item); 419 | } 420 | return false; 421 | 422 | case InVmOpCode.GETG: 423 | InVmValue* name = state.stack.pop(); 424 | if (name && name.getType() == InVmValueType.str) { 425 | 426 | InVmValue* val = this.getGlobalState().get(name.str); 427 | if (val) { 428 | state.stack.push(*val); 429 | return true; 430 | } 431 | } 432 | return false; 433 | 434 | 435 | } 436 | } 437 | 438 | /** 439 | Runs code 440 | 441 | Returns the depth of the stack on completion. 442 | */ 443 | void run() { 444 | while(this.runOne()) { } 445 | } 446 | 447 | this() { 448 | state.stack = nogc_new!InVmValueStack(); 449 | state.callStack = nogc_new!InVmCallStack(); 450 | } 451 | 452 | public: 453 | 454 | ~this() { 455 | nogc_delete(state.stack); 456 | nogc_delete(state.callStack); 457 | } 458 | 459 | /** 460 | Gets global value 461 | */ 462 | InVmValue* getGlobal(nstring name) { 463 | return getGlobalState().get(name); 464 | } 465 | 466 | /** 467 | Sets global value 468 | */ 469 | void setGlobal(nstring name, InVmValue value, bool writeProtect=false) { 470 | getGlobalState().set!true(name, value, writeProtect); 471 | } 472 | 473 | /** 474 | Pushes a float to the stack 475 | */ 476 | final 477 | void push(float f32) { 478 | state.stack.push(InVmValue(f32)); 479 | } 480 | 481 | /** 482 | Pushes a string to the stack 483 | */ 484 | final 485 | void push(nstring str) { 486 | state.stack.push(InVmValue(str)); 487 | } 488 | 489 | /** 490 | Pushes a value to the stack 491 | */ 492 | final 493 | void push(InVmValue val) { 494 | state.stack.push(val); 495 | } 496 | 497 | /** 498 | Pops a value from the stack 499 | */ 500 | final 501 | InVmValue* peek(ptrdiff_t offset) { 502 | return state.stack.peek(offset); 503 | } 504 | 505 | /** 506 | Pops a value from the stack 507 | */ 508 | final 509 | InVmValue* pop() { 510 | return state.stack.pop(); 511 | } 512 | 513 | /** 514 | Pops a range of values off the stack 515 | */ 516 | final 517 | void pop(ptrdiff_t offset, size_t count) { 518 | state.stack.pop(offset, count); 519 | } 520 | 521 | /** 522 | Gets depth of stack 523 | */ 524 | final 525 | size_t getStackDepth() { 526 | return state.stack.getDepth(); 527 | } 528 | 529 | /** 530 | Executes code in global scope. 531 | 532 | Returns size of stack after operation. 533 | Returns -1 on error. 534 | */ 535 | int execute(ubyte[] bytecode) { 536 | if (state.callStack.getDepth() == 0) { 537 | state.bc = bytecode; 538 | state.pc = 0; 539 | 540 | // Run and get return values 541 | this.run(); 542 | 543 | // Reset code and program counter. 544 | state.pc = 0; 545 | state.bc = null; 546 | return cast(int)this.getStackDepth(); 547 | } 548 | return -1; 549 | } 550 | 551 | /** 552 | Calls a global function 553 | 554 | Returns return value count. 555 | Returns -1 on error. 556 | */ 557 | int call(nstring gfunc) { 558 | InVmValue* v = this.getGlobal(gfunc); 559 | if (v && v.isCallable()) { 560 | if (this._vcall(v) && v.isBytecodeBlob()) { 561 | this.run(); 562 | } 563 | return cast(int)this.getStackDepth(); 564 | } 565 | return -1; 566 | } 567 | } 568 | 569 | class InVmVM : InVmState { 570 | @nogc: 571 | private: 572 | shared_ptr!_vmGlobalState globalState; 573 | 574 | protected: 575 | override 576 | _vmGlobalState* getGlobalState() { 577 | return globalState.get(); 578 | } 579 | 580 | public: 581 | this() { 582 | this.globalState = shared_new!_vmGlobalState; 583 | } 584 | } 585 | 586 | 587 | // 588 | // UNIT TESTS 589 | // 590 | 591 | import inochi2d.core.expr.compiler.builder : InVmBytecodeBuilder; 592 | 593 | @("VM: NATIVE CALL") 594 | unittest { 595 | import inmath.math : sin; 596 | 597 | // Sin function 598 | static int mySinFunc(ref InVmState state) @nogc { 599 | InVmValue* v = state.pop(); 600 | if (v && v.isNumeric) { 601 | state.push(sin(v.number)); 602 | return 1; 603 | } 604 | return 0; 605 | } 606 | 607 | // Instantiate VM 608 | InVmVM vm = new InVmVM(); 609 | vm.setGlobal(nstring("sin"), InVmValue(&mySinFunc)); 610 | 611 | vm.push(1.0); 612 | int retValCount = vm.call(nstring("sin")); 613 | import std.stdio : writeln; 614 | assert(retValCount == 1); 615 | assert(vm.getStackDepth() == retValCount); 616 | assert(vm.pop().number == sin(1.0f)); 617 | } 618 | 619 | @("VM: ADD") 620 | unittest { 621 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 622 | builder.buildADD(); 623 | builder.buildRET(); 624 | 625 | // Instantiate VM 626 | InVmVM vm = new InVmVM(); 627 | vm.setGlobal(nstring("add"), InVmValue(builder.finalize())); 628 | 629 | vm.push(32.0); 630 | vm.push(32.0); 631 | int retValCount = vm.call(nstring("add")); 632 | 633 | assert(retValCount == 1); 634 | assert(vm.getStackDepth() == retValCount); 635 | assert(vm.pop().number == 64.0f); 636 | } 637 | 638 | @("VM: SUB") 639 | unittest { 640 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 641 | builder.buildSUB(); 642 | builder.buildRET(); 643 | 644 | // Instantiate VM 645 | InVmVM vm = new InVmVM(); 646 | vm.setGlobal(nstring("sub"), InVmValue(builder.finalize())); 647 | 648 | vm.push(32.0); 649 | vm.push(32.0); 650 | int retValCount = vm.call(nstring("sub")); 651 | 652 | assert(retValCount == 1); 653 | assert(vm.getStackDepth() == retValCount); 654 | assert(vm.pop().number == 0.0f); 655 | } 656 | 657 | @("VM: DIV") 658 | unittest { 659 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 660 | builder.buildDIV(); 661 | builder.buildRET(); 662 | 663 | // Instantiate VM 664 | InVmVM vm = new InVmVM(); 665 | vm.setGlobal(nstring("div"), InVmValue(builder.finalize())); 666 | 667 | vm.push(32.0); 668 | vm.push(2.0); 669 | int retValCount = vm.call(nstring("div")); 670 | 671 | assert(retValCount == 1); 672 | assert(vm.getStackDepth() == retValCount); 673 | assert(vm.pop().number == 16.0f); 674 | } 675 | 676 | @("VM: MUL") 677 | unittest { 678 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 679 | builder.buildMUL(); 680 | builder.buildRET(); 681 | 682 | // Instantiate VM 683 | InVmVM vm = new InVmVM(); 684 | vm.setGlobal(nstring("mul"), InVmValue(builder.finalize())); 685 | 686 | vm.push(32.0); 687 | vm.push(2.0); 688 | int retValCount = vm.call(nstring("mul")); 689 | 690 | assert(retValCount == 1); 691 | assert(vm.getStackDepth() == retValCount); 692 | assert(vm.pop().number == 64.0f); 693 | } 694 | 695 | @("VM: MOD") 696 | unittest { 697 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 698 | builder.buildMOD(); 699 | builder.buildRET(); 700 | 701 | // Instantiate VM 702 | InVmVM vm = new InVmVM(); 703 | vm.setGlobal(nstring("mod"), InVmValue(builder.finalize())); 704 | 705 | vm.push(32.0); 706 | vm.push(16.0); 707 | int retValCount = vm.call(nstring("mod")); 708 | 709 | assert(retValCount == 1); 710 | assert(vm.getStackDepth() == retValCount); 711 | assert(vm.pop().number == 0.0f); 712 | } 713 | 714 | @("VM: JSR NATIVE") 715 | unittest { 716 | import std.stdio : writeln; 717 | import inmath.math : sin; 718 | 719 | // Sin function 720 | static int mySinFunc(ref InVmState state) @nogc { 721 | InVmValue* v = state.pop(); 722 | if (v && v.isNumeric) { 723 | state.push(sin(v.number)); 724 | return 1; 725 | } 726 | return 0; 727 | } 728 | InVmBytecodeBuilder builder = nogc_new!InVmBytecodeBuilder(); 729 | 730 | // Parameters 731 | builder.buildPUSH(1.0); 732 | 733 | // Function get 734 | builder.buildPUSH("sin"); 735 | builder.buildGETG(); 736 | 737 | // Jump 738 | builder.buildJSR(); 739 | builder.buildRET(); 740 | 741 | // Instantiate VM 742 | InVmVM vm = new InVmVM(); 743 | vm.setGlobal(nstring("sin"), InVmValue(&mySinFunc)); 744 | vm.setGlobal(nstring("bcfunc"), InVmValue(builder.finalize())); 745 | 746 | int retValCount = vm.call(nstring("bcfunc")); 747 | 748 | assert(retValCount == 1); 749 | assert(vm.getStackDepth() == retValCount); 750 | assert(vm.pop().number == sin(1.0f)); 751 | } -------------------------------------------------------------------------------- /source/inochi2d/core/io/obj.d: -------------------------------------------------------------------------------- 1 | module inochi2d.core.io.obj; 2 | import inochi2d.core.io.serializer; 3 | 4 | public import inochi2d.core.io.tree.value; 5 | public import inochi2d.core.io.tree.ctx; 6 | 7 | /** 8 | An Inochi2D Object which can be serialized and deserialized. 9 | */ 10 | abstract 11 | class InObject { 12 | @nogc: 13 | protected: 14 | this() { } 15 | 16 | public: 17 | 18 | /** 19 | Serialize the object 20 | */ 21 | abstract void serialize(ref InTreeValue node, ref InDataContext context); 22 | 23 | /** 24 | Deserialize the object 25 | */ 26 | abstract void deserialize(ref InTreeValue node, ref InDataContext context); 27 | 28 | /** 29 | Finalize the object 30 | */ 31 | abstract void finalize(ref InTreeValue node, ref InDataContext context); 32 | 33 | /** 34 | Creates a copy of the object 35 | */ 36 | abstract InObject copy(); 37 | } -------------------------------------------------------------------------------- /source/inochi2d/core/io/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Puppet reading and writing. 10 | */ 11 | module inochi2d.core.io; 12 | 13 | public import inochi2d.core.io.obj; 14 | public import inochi2d.core.io.serializer; 15 | public import inochi2d.core.io.tree; 16 | -------------------------------------------------------------------------------- /source/inochi2d/core/io/serializer.d: -------------------------------------------------------------------------------- 1 | module inochi2d.core.io.serializer; 2 | import numem.all; 3 | 4 | /** 5 | The base class for serializers which can serialize Inochi2D Puppets 6 | */ 7 | abstract 8 | class InIOSerializerContext { 9 | @nogc: 10 | private: 11 | set!(nstring) flags; 12 | 13 | public: 14 | 15 | /** 16 | Gets whether a flag is set 17 | */ 18 | final 19 | @safe 20 | bool hasFlag(nstring flag) { 21 | return flags.contains(flag); 22 | } 23 | 24 | /** 25 | Sets a flag 26 | */ 27 | final 28 | @safe 29 | void setFlag(nstring flag, bool on=true) { 30 | if (on && !flags.contains(flag)) { 31 | flags.insert(flag); 32 | } else if (!on && flags.contains(flag)) { 33 | flags.remove(flag); 34 | } 35 | } 36 | 37 | /** 38 | Gets an iterator over the flags 39 | */ 40 | final 41 | @trusted 42 | auto getFlags() { 43 | return flags.byKey(); 44 | } 45 | } 46 | 47 | /** 48 | Base class for all tree traversal 49 | */ 50 | abstract 51 | class InIOTreeContext { 52 | @nogc: 53 | abstract void enter(nstring name); 54 | } -------------------------------------------------------------------------------- /source/inochi2d/core/io/tree/ctx.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.io.tree.ctx; 9 | import inochi2d.core.io.tree.value; 10 | import numem.core.uuid; 11 | import numem.all; 12 | import inochi2d.puppet.node; 13 | 14 | /** 15 | Context for serialization and deserialization 16 | */ 17 | class InDataContext { 18 | @nogc: 19 | private: 20 | set!(nstring) flags; 21 | map!(uint, UUID) mappings; 22 | InTreeValue root; 23 | 24 | 25 | public: 26 | ~this() { 27 | nogc_delete(flags); 28 | } 29 | 30 | this() { 31 | root = InTreeValue.newObject(); 32 | } 33 | 34 | /** 35 | Gets whether a flag is set 36 | */ 37 | final 38 | @safe 39 | bool hasFlag(nstring flag) { 40 | return flags.contains(flag); 41 | } 42 | 43 | /** 44 | Maps a legacy id to a UUID 45 | */ 46 | UUID mapLegacyID(uint id) { 47 | mappings[id] = inNewUUID(); 48 | return mappings[id]; 49 | } 50 | 51 | /** 52 | Gets the UUID the legacy ID maps to 53 | */ 54 | UUID getMappingFor(uint id) { 55 | if (id in mappings) return mappings[id]; 56 | return UUID.nil; 57 | } 58 | 59 | /** 60 | Sets a flag 61 | */ 62 | final 63 | @safe 64 | void setFlag(nstring flag, bool on=true) { 65 | if (on && !flags.contains(flag)) { 66 | flags.insert(flag); 67 | } else if (!on && flags.contains(flag)) { 68 | flags.remove(flag); 69 | } 70 | } 71 | 72 | /** 73 | Gets an iterator over the flags 74 | */ 75 | final 76 | @trusted 77 | auto getFlags() { 78 | return flags.byKey(); 79 | } 80 | 81 | /** 82 | Gets the root of the serialization context 83 | */ 84 | final 85 | ref InTreeValue getRoot() { 86 | return root; 87 | } 88 | } -------------------------------------------------------------------------------- /source/inochi2d/core/io/tree/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | The Inochi2D Puppet Object Model 10 | 11 | This contains the lower level tree structures used by Inochi2D 12 | to serialize a model to file. 13 | 14 | The contents of an InpNode gets passed to the (de)serialization backend 15 | for saving or loading. 16 | 17 | This allows Inochi2D to both support the older INP 1.0 and the new 18 | INP 2.0 file format w/ the inbf encoding style. 19 | */ 20 | module inochi2d.core.io.tree; 21 | import numem.all; 22 | 23 | public import inochi2d.core.io.tree.value; 24 | public import inochi2d.core.io.tree.ctx; 25 | -------------------------------------------------------------------------------- /source/inochi2d/core/io/tree/value.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.core.io.tree.value; 9 | import std.traits; 10 | import numem.all; 11 | import numem.core.uuid; 12 | 13 | 14 | /** 15 | Enumeration over Inochi2D InTreeValue content types 16 | */ 17 | enum InTreeValueType { 18 | /** 19 | Node not instantiated 20 | */ 21 | nil, 22 | 23 | /** 24 | Node is string 25 | */ 26 | str, 27 | 28 | /** 29 | Node is number 30 | */ 31 | number, 32 | 33 | /** 34 | Node is integer 35 | */ 36 | integer, 37 | 38 | /** 39 | UUID 40 | */ 41 | uuid, 42 | 43 | /** 44 | Node is boolean 45 | */ 46 | boolean, 47 | 48 | /** 49 | Node is a key-value container 50 | */ 51 | node, 52 | 53 | /** 54 | Node is an array container 55 | */ 56 | array, 57 | 58 | /** 59 | Node is a binary blob 60 | */ 61 | blob 62 | } 63 | 64 | /** 65 | A node in the Puppet tree. 66 | 67 | InTreeValues own the memory of their elements 68 | */ 69 | @AllowInitEmpty 70 | struct InTreeValue { 71 | @nogc: 72 | private: 73 | InTreeValueType type; 74 | InTreeValueStore store; 75 | union InTreeValueStore { 76 | @nogc: 77 | nstring str; 78 | double number; 79 | long integer; 80 | bool boolean; 81 | UUID uuid; 82 | weak_map!(nstring, InTreeValue*) node; 83 | weak_vector!(InTreeValue) array; 84 | vector!ubyte blob; 85 | 86 | void destroy(InTreeValueType type) nothrow { 87 | final switch(type) { 88 | 89 | // Destroyed with the tree value 90 | case InTreeValueType.number: 91 | case InTreeValueType.boolean: 92 | case InTreeValueType.integer: 93 | case InTreeValueType.uuid: 94 | case InTreeValueType.nil: 95 | break; 96 | 97 | case InTreeValueType.str: 98 | nogc_delete(str); 99 | break; 100 | 101 | case InTreeValueType.node: 102 | foreach(ref element; node) { 103 | nogc_delete(element); 104 | } 105 | nogc_delete(node); 106 | break; 107 | 108 | case InTreeValueType.array: 109 | foreach(ref element; array) { 110 | nogc_delete(element); 111 | } 112 | nogc_delete(array); 113 | break; 114 | 115 | case InTreeValueType.blob: 116 | nogc_delete(blob); 117 | break; 118 | 119 | } 120 | } 121 | } 122 | 123 | void clear() nothrow { 124 | store.destroy(type); 125 | this.type = InTreeValueType.nil; 126 | } 127 | 128 | public: 129 | 130 | this(ref InTreeValue rhs) nothrow { 131 | this.type = rhs.type; 132 | final switch(rhs.type) { 133 | 134 | case InTreeValueType.number: 135 | store.number = rhs.store.number; 136 | break; 137 | 138 | case InTreeValueType.boolean: 139 | store.boolean = rhs.store.boolean; 140 | break; 141 | 142 | case InTreeValueType.integer: 143 | store.integer = rhs.store.integer; 144 | break; 145 | 146 | case InTreeValueType.uuid: 147 | store.uuid = rhs.store.uuid; 148 | break; 149 | 150 | case InTreeValueType.nil: 151 | break; 152 | 153 | case InTreeValueType.str: 154 | store.str = nstring(rhs.store.str); 155 | break; 156 | 157 | case InTreeValueType.node: 158 | foreach(key; rhs.store.node.byKey()) { 159 | store.node[key] = rhs.store.node[key]; 160 | } 161 | break; 162 | 163 | case InTreeValueType.array: 164 | store.array = weak_vector!(InTreeValue)(rhs.store.array[]); 165 | break; 166 | 167 | case InTreeValueType.blob: 168 | store.blob = vector!(ubyte)(rhs.store.blob); 169 | break; 170 | } 171 | } 172 | 173 | /** 174 | Creates a new object 175 | */ 176 | static InTreeValue newObject() { 177 | InTreeValue node; 178 | node.type = InTreeValueType.node; 179 | return node; 180 | } 181 | 182 | /** 183 | Creates a new array 184 | */ 185 | static InTreeValue newArray() { 186 | InTreeValue node; 187 | node.type = InTreeValueType.array; 188 | return node; 189 | } 190 | 191 | /** 192 | Creates a new blob 193 | */ 194 | static InTreeValue newBlob() { 195 | InTreeValue node; 196 | node.type = InTreeValueType.blob; 197 | return node; 198 | } 199 | 200 | /** 201 | Creates a new float 202 | */ 203 | this(float value) { 204 | this.type = InTreeValueType.number; 205 | store.number = value; 206 | } 207 | 208 | /** 209 | Creates a new uuid 210 | */ 211 | this(UUID value) { 212 | this.type = InTreeValueType.uuid; 213 | store.uuid = value; 214 | } 215 | 216 | /** 217 | Creates a new float 218 | */ 219 | this(long value) { 220 | this.type = InTreeValueType.integer; 221 | store.integer = value; 222 | } 223 | 224 | /** 225 | Creates a new boolean 226 | */ 227 | this(bool value) { 228 | this.type = InTreeValueType.boolean; 229 | store.boolean = value; 230 | } 231 | 232 | /** 233 | Creates a new string 234 | */ 235 | this(nstring value) { 236 | this.type = InTreeValueType.str; 237 | store.str = value; 238 | } 239 | 240 | /** 241 | Creates a new string 242 | */ 243 | this(string value) { 244 | this.type = InTreeValueType.str; 245 | store.str = nstring(value); 246 | } 247 | 248 | /** 249 | Creates a new blob 250 | */ 251 | this(vector!ubyte value) { 252 | this.type = InTreeValueType.blob; 253 | store.blob = vector!(ubyte)(value); 254 | } 255 | 256 | /** 257 | Destructor 258 | */ 259 | ~this() nothrow { 260 | this.clear(); 261 | } 262 | 263 | /** 264 | Assignment operator 265 | */ 266 | auto opAssign(nstring value) { 267 | if (this.isString()) { 268 | nogc_delete(store.str); 269 | store.str = nstring(value); 270 | } 271 | return this; 272 | } 273 | 274 | /** 275 | Assignment operator 276 | */ 277 | auto opAssign(bool value) { 278 | if (this.isBoolean()) { 279 | store.boolean = value; 280 | } 281 | 282 | return this; 283 | } 284 | 285 | /** 286 | Assignment operator 287 | */ 288 | auto opAssign(float value) { 289 | if (this.isNumber()) { 290 | store.number = value; 291 | } 292 | 293 | return this; 294 | } 295 | 296 | /** 297 | Index tree value by key 298 | */ 299 | ref auto opIndex(string key) { 300 | return this.opIndex(nstring(key)); 301 | } 302 | 303 | /** 304 | Index tree value by key 305 | */ 306 | ref auto opIndex(nstring key) { 307 | if (type != InTreeValueType.node) return InTreeValue.init; 308 | if (key !in store.node) return InTreeValue.init; 309 | 310 | return *store.node[key]; 311 | } 312 | 313 | /** 314 | Assign tree value element by key (static slice) 315 | */ 316 | void opIndexAssign(InTreeValue node, string key) { 317 | this.opIndexAssign(node, nstring(key)); 318 | } 319 | 320 | /** 321 | Assign tree value element by key 322 | */ 323 | void opIndexAssign(InTreeValue node, nstring key) { 324 | if (this.type != InTreeValueType.node) 325 | return; 326 | 327 | // Replacement 328 | if (key in store.node) { 329 | store.node.remove(key); 330 | } 331 | 332 | // Removal 333 | if (node.type == InTreeValueType.nil) 334 | return; 335 | 336 | // Addition 337 | store.node[key] = nogc_new!(InTreeValue)(node); 338 | return; 339 | } 340 | 341 | /** 342 | Index tree value by index 343 | */ 344 | auto opIndex(size_t index) { 345 | if (type != InTreeValueType.array) return InTreeValue.init; 346 | if (index >= store.array.size()) return InTreeValue.init; 347 | 348 | return store.array[index]; 349 | } 350 | 351 | /** 352 | Assign tree value element by index 353 | */ 354 | void opIndexAssign(InTreeValue node, size_t index) { 355 | if (this.type != InTreeValueType.array) 356 | return; 357 | 358 | if (index >= store.array.size()) 359 | return; 360 | 361 | store.array[index] = node; 362 | return; 363 | } 364 | 365 | /** 366 | Array append 367 | */ 368 | void opOpAssign(string op: "~", T)(T value) { 369 | if (type != InTreeValueType.array) return; 370 | 371 | static if (is(value : InTreeValue)) { 372 | array ~= value; 373 | } else { 374 | InTreeValue nvalue; 375 | nvalue = value; 376 | store.array ~= nvalue; 377 | } 378 | } 379 | 380 | /** 381 | Gets a reference to the binary blob 382 | 383 | Returns null if the value is not a blob. 384 | */ 385 | vector!(ubyte)* getBlobRef() { 386 | if (this.isBlob) 387 | return &store.blob; 388 | return null; 389 | } 390 | 391 | /** 392 | Gets a reference to a tree value in the node 393 | 394 | Returns null if the value is not a node. 395 | */ 396 | InTreeValue* getNodeRef(string key) { 397 | return getNodeRef(nstring(key)); 398 | } 399 | 400 | /** 401 | Gets a reference to a tree value in the node 402 | 403 | Returns null if the value is not a node. 404 | */ 405 | InTreeValue* getNodeRef(nstring key) { 406 | if (type != InTreeValueType.node) return null; 407 | if (key !in store.node) return null; 408 | 409 | return store.node[key]; 410 | } 411 | 412 | /** 413 | Gets the value stored in this treevalue. 414 | */ 415 | T get(T)() { 416 | final switch(type) { 417 | case InTreeValueType.node: 418 | case InTreeValueType.array: 419 | case InTreeValueType.nil: 420 | break; 421 | 422 | case InTreeValueType.number: 423 | static if (isNumeric!T) 424 | return cast(T)store.number; 425 | else 426 | break; 427 | 428 | case InTreeValueType.uuid: 429 | static if (is(T == UUID)) 430 | return store.uuid; 431 | else 432 | break; 433 | 434 | case InTreeValueType.integer: 435 | static if (isNumeric!T) 436 | return cast(T)store.integer; 437 | else 438 | break; 439 | 440 | case InTreeValueType.boolean: 441 | static if (isNumeric!T) 442 | return cast(T)store.boolean; 443 | else static if (is(T == bool)) 444 | return cast(T)store.boolean; 445 | else 446 | break; 447 | 448 | case InTreeValueType.str: 449 | 450 | // Slice 451 | static if (is(T == string)) 452 | return cast(string)store.str[]; 453 | 454 | // Copy 455 | else static if (is(T == nstring)) 456 | return nstring(store.str); 457 | 458 | else 459 | break; 460 | 461 | case InTreeValueType.blob: 462 | 463 | // Slice 464 | static if (is(T == ubyte[])) 465 | return store.blob[]; 466 | 467 | // Copy 468 | else static if (is(T == vector!(ubyte))) 469 | return vector!(ubyte)(store.blob); 470 | 471 | else 472 | break; 473 | } 474 | 475 | return T.init; 476 | } 477 | 478 | /** 479 | Gets the length (amount of elements) in a node. 480 | */ 481 | size_t getLength() { 482 | switch(type) { 483 | default: 484 | return 0; 485 | case InTreeValueType.node: 486 | return store.node.length(); 487 | case InTreeValueType.array: 488 | return store.array.size(); 489 | case InTreeValueType.blob: 490 | return store.blob.size(); 491 | } 492 | } 493 | 494 | /** 495 | Iterates the node by its keys 496 | */ 497 | auto byKey() { 498 | switch(type) { 499 | case InTreeValueType.node: 500 | return store.node.byKey(); 501 | default: 502 | return ReturnType!(store.node.byKey).init; 503 | } 504 | } 505 | 506 | /** 507 | Gets whether the tree value has a length; 508 | that is to say that the tree value contains multiple elements. 509 | */ 510 | bool hasLength() { 511 | switch(type) { 512 | default: 513 | return false; 514 | case InTreeValueType.node: 515 | return true; 516 | case InTreeValueType.array: 517 | return true; 518 | case InTreeValueType.blob: 519 | return true; 520 | } 521 | } 522 | 523 | /** 524 | Gets the type of the tree value. 525 | */ 526 | InTreeValueType getType() { 527 | return type; 528 | } 529 | 530 | /** 531 | Gets whether the tree value is a node 532 | */ 533 | bool isNode() { 534 | return type == InTreeValueType.node; 535 | } 536 | 537 | /** 538 | Gets whether the tree value is an array 539 | */ 540 | bool isArray() { 541 | return type == InTreeValueType.array; 542 | } 543 | 544 | /** 545 | Gets whether the tree value is a binary blob of data 546 | */ 547 | bool isBlob() { 548 | return type == InTreeValueType.blob; 549 | } 550 | 551 | /** 552 | Gets whether the tree value is a number 553 | */ 554 | bool isNumber() { 555 | return type == InTreeValueType.number; 556 | } 557 | 558 | /** 559 | Gets whether the tree value is a number 560 | */ 561 | bool isInteger() { 562 | return type == InTreeValueType.integer; 563 | } 564 | 565 | /** 566 | Gets whether the tree value is a string 567 | */ 568 | bool isString() { 569 | return type == InTreeValueType.str; 570 | } 571 | 572 | /** 573 | Gets whether the tree value is a boolean 574 | */ 575 | bool isBoolean() { 576 | return type == InTreeValueType.boolean; 577 | } 578 | 579 | /** 580 | Gets whether the tree value has any valid values 581 | */ 582 | bool isValid() { 583 | return type != InTreeValueType.nil; 584 | } 585 | } 586 | 587 | @("InTreeValue (number)") 588 | unittest { 589 | InTreeValue value = 42.0f; 590 | assert(value.get!float == 42.0f); 591 | } 592 | 593 | @("InTreeValue (bool)") 594 | unittest { 595 | InTreeValue value = false; 596 | assert(value.get!bool == false); 597 | } 598 | 599 | @("InTreeValue (string)") 600 | unittest { 601 | const string tmp = "Hello, world!"; 602 | 603 | InTreeValue value = tmp; 604 | assert(value.get!string == tmp[]); 605 | assert(value.get!nstring == nstring(tmp)); 606 | } 607 | 608 | @("InTreeValue (object)") 609 | unittest { 610 | const float tmpFloat = 42.0f; 611 | const bool tmpBool = true; 612 | const string tmpStr = "Hello, world!"; 613 | 614 | // Create object 615 | InTreeValue object = InTreeValue.newObject(); 616 | object["a"] = InTreeValue(tmpFloat); 617 | object["b"] = InTreeValue(tmpBool); 618 | object["c"] = InTreeValue(tmpStr); 619 | 620 | // Check validity 621 | assert(object.isValid(), "Object not valid!"); 622 | assert(object.isNode(), "Object not an object?!"); 623 | 624 | // Check element validity 625 | assert(object["a"].isValid(), "Number item 'a' returned invalid tree value!"); 626 | assert(object["b"].isValid(), "Boolean item 'b' returned invalid tree value!"); 627 | assert(object["c"].isValid(), "String item 'c' returned invalid tree value!"); 628 | 629 | // Check element values 630 | assert(object["a"].get!float() == tmpFloat, "Number item 'a' return wrong value!"); 631 | assert(object["b"].get!bool() == tmpBool, "Boolean item 'b' return wrong value!"); 632 | assert(object["c"].get!string() == tmpStr, "String item 'c' return wrong value!"); 633 | } 634 | 635 | @("InTreeValue (object, get invalid)") 636 | unittest { 637 | InTreeValue object = InTreeValue.newObject(); 638 | assert(object["invalid"].isValid() == false); 639 | } -------------------------------------------------------------------------------- /source/inochi2d/ffi/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Inochi2D C Interface 10 | */ 11 | module inochi2d.ffi; 12 | 13 | -------------------------------------------------------------------------------- /source/inochi2d/ffi/puppet.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.ffi.puppet; 9 | import inochi2d.puppet.puppet; 10 | 11 | @nogc extern(C): 12 | 13 | /// Scene reference 14 | alias InPuppetRef = void*; 15 | -------------------------------------------------------------------------------- /source/inochi2d/ffi/scene.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | module inochi2d.ffi.scene; 8 | import inochi2d.ffi.puppet; 9 | 10 | import inochi2d.puppet.scene; 11 | import inochi2d.puppet.puppet; 12 | 13 | import numem.all; 14 | 15 | import core.stdc.stdio : FILE; 16 | 17 | @nogc extern(C) export: 18 | 19 | /// Scene reference 20 | alias InSceneRef = void*; 21 | 22 | /** 23 | Creates a scene 24 | */ 25 | InSceneRef inSceneCreate() { 26 | return cast(InSceneRef)nogc_new!Scene(); 27 | } 28 | 29 | /** 30 | Destroys and frees a scene 31 | */ 32 | void inSceneDestroy(InSceneRef scene) { 33 | if (scene) { 34 | Scene _scene = cast(Scene)scene; 35 | nogc_delete!Scene(_scene); 36 | } 37 | } 38 | 39 | /** 40 | Loads a puppet from a C file. 41 | */ 42 | InPuppetRef inSceneLoadPuppetFromFile(InSceneRef scene, FILE* file) { 43 | FileStream strean = nogc_new!FileStream(file); 44 | Puppet puppet = (cast(Scene)scene).loadPuppet(strean); 45 | 46 | nogc_delete(strean); 47 | return cast(InPuppetRef)puppet; 48 | } 49 | 50 | /** 51 | Loads a puppet from memory 52 | */ 53 | InPuppetRef inSceneLoadPuppetFromMemory(InSceneRef scene, ubyte* data, size_t length) { 54 | MemoryStream strean = nogc_new!MemoryStream(data, length); 55 | Puppet puppet = (cast(Scene)scene).loadPuppet(strean); 56 | 57 | nogc_delete(strean); 58 | return cast(InPuppetRef)puppet; 59 | } -------------------------------------------------------------------------------- /source/inochi2d/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Inochi2D SDK 10 | */ 11 | module inochi2d; 12 | 13 | public import inochi2d.core.draw; 14 | public import inochi2d.core.io; 15 | public import inochi2d.puppet; -------------------------------------------------------------------------------- /source/inochi2d/puppet/node.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | module inochi2d.puppet.node; 8 | import inochi2d.core.io; 9 | import numem.all; 10 | import numem.core.uuid; 11 | import numem.core.random; 12 | 13 | @nogc: 14 | 15 | private { 16 | __gshared Random _inRandom; 17 | } 18 | 19 | /** 20 | An Inochi2D Node 21 | */ 22 | class Node : InObject { 23 | @nogc: 24 | private: 25 | UUID uuid; 26 | 27 | // LEGACY 28 | uint id; 29 | 30 | // Node tree primitives. 31 | weak_vector!Node children; 32 | Node parent; 33 | 34 | protected: 35 | 36 | /** 37 | Implement copy of self 38 | */ 39 | Node selfCopy() { 40 | Node theCopy = nogc_new!Node; 41 | theCopy.uuid = inNewUUID(); 42 | return theCopy; 43 | } 44 | 45 | Node findChild(UUID uuid) { 46 | foreach(child; children) { 47 | if (child.uuid == uuid) return child; 48 | } 49 | return null; 50 | } 51 | 52 | public: 53 | 54 | // Base constructor 55 | this() { 56 | 57 | } 58 | 59 | /** 60 | Serialize the object 61 | */ 62 | override 63 | void serialize(ref InTreeValue node, ref InDataContext context) { 64 | node["uuid"] = InTreeValue(uuid); 65 | 66 | InTreeValue childrenArray = InTreeValue.newArray(); 67 | foreach(ref child; children) { 68 | 69 | // Serialize child 70 | InTreeValue childNode = InTreeValue.newObject(); 71 | child.serialize(childNode, context); 72 | 73 | // Add to children 74 | childrenArray ~= childNode; 75 | } 76 | 77 | // Add children to this node 78 | node["children"] = childrenArray; 79 | } 80 | 81 | /** 82 | Deserialize the object 83 | */ 84 | override 85 | void deserialize(ref InTreeValue node, ref InDataContext context) { 86 | 87 | if (node["id"].isValid()) { 88 | // Inochi2D 0.8 89 | this.id = node["id"].get!int; 90 | this.uuid = context.mapLegacyID(id); 91 | } else { 92 | // Inochi2D 0.9 93 | this.uuid = node["uuid"].get!UUID; 94 | } 95 | 96 | // Iterate children 97 | if (node["children"].isValid()) { 98 | foreach(i; 0..node["children"].getLength()) { 99 | 100 | Node n; 101 | 102 | // Get children 103 | InTreeValue childTree = node["children"][i]; 104 | n.deserialize(childTree, context); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | Deserialize the object 111 | */ 112 | override 113 | void finalize(ref InTreeValue node, ref InDataContext context) { 114 | 115 | // Iterate children, at least if there is any. 116 | if (node["children"].isValid()) { 117 | foreach(i; 0..node["children"].getLength()) { 118 | 119 | // Get children 120 | InTreeValue childTree = node["children"][i]; 121 | 122 | // Handle UUID mapping for child nodes 123 | // Including 0.8 nodes 124 | UUID childNodeUUID; 125 | if (childTree["id"].isValid()) { 126 | 127 | // Inochi2D 0.8 128 | childNodeUUID = context.getMappingFor(childTree["id"].get!uint); 129 | 130 | } else { 131 | 132 | // Inochi2D 0.9 133 | childNodeUUID = childTree["uuid"].get!UUID; 134 | } 135 | 136 | // Finally iterate children. 137 | if (Node child = this.findChild(childNodeUUID)) { 138 | 139 | // We've instantiated the child, finalize it. 140 | child.finalize(childTree, context); 141 | } 142 | } 143 | } 144 | } 145 | 146 | /** 147 | Creates a copy of the object. 148 | 149 | This should be a deep copy. 150 | */ 151 | override 152 | InObject copy() { 153 | Node obj = selfCopy(); 154 | 155 | // Copy all children as well 156 | foreach(ref child; children) { 157 | obj.children ~= cast(Node)child.copy(); 158 | } 159 | 160 | return obj; 161 | } 162 | 163 | } 164 | 165 | /** 166 | Creates a new UUID 167 | */ 168 | UUID inNewUUID() { 169 | return UUID.createRandom(_inRandom); 170 | } 171 | 172 | 173 | /** 174 | Initializes the nodes subsystem 175 | 176 | Is called automatically by inInit 177 | */ 178 | void inInitNodes() { 179 | _inRandom = nogc_new!Random(); 180 | } -------------------------------------------------------------------------------- /source/inochi2d/puppet/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | /** 9 | Implementation of the Inochi2D Puppet Specification. 10 | */ 11 | module inochi2d.puppet; 12 | 13 | public import inochi2d.puppet.node; 14 | public import inochi2d.puppet.puppet; 15 | public import inochi2d.puppet.scene; -------------------------------------------------------------------------------- /source/inochi2d/puppet/puppet.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.puppet.puppet; 9 | import inochi2d.puppet.node; 10 | import inochi2d.core.io.obj; 11 | 12 | @nogc: 13 | 14 | /** 15 | An Inochi2D Puppet 16 | */ 17 | class Puppet : InObject { 18 | @nogc: 19 | private: 20 | Node root; 21 | 22 | public: 23 | 24 | /** 25 | Serialize the object 26 | */ 27 | void serialize(ref InDataContext context) { 28 | InTreeValue treeRoot = context.getRoot(); 29 | 30 | treeRoot["rigging_data"] = InTreeValue.newObject(); 31 | this.serialize(*treeRoot.getNodeRef("rigging_data"), context); 32 | } 33 | 34 | /** 35 | Serialize the object 36 | */ 37 | override 38 | void serialize(ref InTreeValue node, ref InDataContext context) { 39 | InTreeValue nodes = InTreeValue.newObject(); 40 | root.serialize(nodes, context); 41 | node["nodes"] = nodes; 42 | } 43 | 44 | /** 45 | Deserialize the object 46 | */ 47 | override 48 | void deserialize(ref InTreeValue node, ref InDataContext context) { } 49 | 50 | /** 51 | Finalize the object 52 | */ 53 | override 54 | void finalize(ref InTreeValue node, ref InDataContext context) { 55 | 56 | } 57 | 58 | /** 59 | Puppets cannot be copied. 60 | */ 61 | override 62 | InObject copy() { return null; } 63 | 64 | } -------------------------------------------------------------------------------- /source/inochi2d/puppet/scene.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024, Inochi2D Project 3 | Distributed under the 2-Clause BSD License, see LICENSE file. 4 | 5 | Authors: Luna the Foxgirl 6 | */ 7 | 8 | module inochi2d.puppet.scene; 9 | import inochi2d.puppet.puppet; 10 | import inochi2d.core.draw; 11 | import inochi2d.core.draw.list : DrawList; 12 | import numem.all; 13 | 14 | @nogc: 15 | 16 | /** 17 | An Inochi2D Scene 18 | 19 | Scenes contains and manages puppets, 20 | loading and unloading of puppets needs to happen within a scene. 21 | 22 | Scenes owns the memory of a puppet and removing a puppet from a scene will free 23 | it and its resources from memory. 24 | */ 25 | class Scene { 26 | @nogc: 27 | private: 28 | DrawList list; 29 | vector!Puppet puppets; 30 | 31 | public: 32 | 33 | /// Destructor 34 | ~this() { 35 | nogc_delete(list); 36 | nogc_delete(puppets); 37 | } 38 | 39 | /// Constructor 40 | this() { 41 | list = nogc_new!DrawList(); 42 | } 43 | 44 | /** 45 | Gets the command queue associated with this scene. 46 | */ 47 | ref DrawList getDrawList() { 48 | return list; 49 | } 50 | 51 | /** 52 | Loads a puppet from the specified source. 53 | */ 54 | Puppet loadPuppet(Stream from) { 55 | // TODO: Actually load 56 | return null; 57 | } 58 | 59 | /** 60 | Unloads the specified puppet 61 | */ 62 | void unloadPuppet(Puppet puppet) { 63 | foreach(idx, _lpuppet; puppets) { 64 | 65 | // Pointer comparison 66 | if (_lpuppet is puppet) { 67 | puppets.remove(idx); 68 | return; 69 | } 70 | } 71 | } 72 | 73 | /** 74 | Gets the amount of loaded puppets. 75 | */ 76 | size_t getPuppetCount() { 77 | return puppets.size(); 78 | } 79 | 80 | /** 81 | Gets puppets loaded in to this scene. 82 | */ 83 | Puppet[] getLoadedPuppets() { 84 | return puppets[0..$]; 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /source/inochi2d/ver.d: -------------------------------------------------------------------------------- 1 | // AUTOGENERATED BY GITVER, DO NOT MODIFY 2 | module inochi2d.ver; 3 | 4 | /** 5 | Inochi2D Version, autogenerated with gitver 6 | */ 7 | enum IN_VERSION = "v0.9.0"; 8 | 9 | // trans rights -------------------------------------------------------------------------------- /vcvars.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-VSDevEnvironment { 2 | $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" 3 | $installationPath = & $vswhere -prerelease -legacy -latest -property installationPath 4 | $Command = Join-Path $installationPath "Common7\Tools\vsdevcmd.bat" 5 | & "${env:COMSPEC}" /s /c "`"$Command`" -no_logo && set" | Foreach-Object { 6 | if ($_ -match '^([^=]+)=(.*)') { 7 | [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2]) 8 | } 9 | } 10 | } 11 | Invoke-VSDevEnvironment --------------------------------------------------------------------------------