├── .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 | [](https://patreon.com/clipsey)
7 | [](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 | [](https://patreon.com/clipsey)
10 | [](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 | [
](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 | [](https://patreon.com/clipsey)
9 | Discord 社区:
10 | [](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
--------------------------------------------------------------------------------