├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── edsl-proc-macro.md └── imgs │ └── example.png ├── rustfmt.toml ├── shades-edsl ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── lib.rs │ └── syntax.rs └── shades ├── CHANGELOG.md ├── Cargo.toml ├── src ├── builtin.rs ├── env.rs ├── erased.rs ├── expr.rs ├── fun.rs ├── input.rs ├── lib.rs ├── output.rs ├── scope.rs ├── shader.rs ├── stage.rs ├── stdlib.rs ├── swizzle.rs ├── types.rs ├── var.rs ├── writer.rs └── writer │ └── glsl.rs └── tests └── shades.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Switch to the nightly compiler 13 | run: rustup default nightly 14 | - name: Build 15 | run: cargo build 16 | 17 | quality: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install dependencies 22 | run: rustup component add rustfmt 23 | - name: rustfmt 24 | run: cargo fmt -- --check 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document is the official contribution guide contributors must follow. It will be **greatly appreciated** if you 4 | read it first before contributing. It will also prevent you from losing your time if you open an issue / make a PR that 5 | doesn’t comply to this document. 6 | 7 | 8 | 9 | * [Disclaimer and why this document](#disclaimer-and-why-this-document) 10 | * [How to make a change](#how-to-make-a-change) 11 | * [Process](#process) 12 | * [Conventions](#conventions) 13 | * [Coding](#coding) 14 | * [Git](#git) 15 | * [Git message](#git-message) 16 | * [Commit atomicity](#commit-atomicity) 17 | * [Hygiene](#hygiene) 18 | * [Release process](#release-process) 19 | * [Overall process](#overall-process) 20 | * [Changelogs update](#changelogs-update) 21 | * [Git tag](#git-tag) 22 | * [Support and donation](#support-and-donation) 23 | 24 | 25 | 26 | # Disclaimer and why this document 27 | 28 | People contributing is awesome. The more people contribute to Free & Open-Source software, the better the 29 | world is to me. However, the more people contribute, the more work we have to do on our spare-time. Good 30 | contributions are highly appreciated, especially if they thoroughly follow the conventions and guidelines of 31 | each and every repository. However, bad contributions — that don’t follow this document, for instance — are 32 | going to require me more work than was involved into making the actual change. It’s even worse when the contribution 33 | actually solves a bug or add a new feature. 34 | 35 | So please read this document; it’s not hard and the few rules here are easy to respect. You might already do 36 | everything in this list anyway, but reading it won’t hurt you. For more junior / less-experienced developers, it’s 37 | very likely you will learn a bit of process that is considered good practice, especially when working with VCS like 38 | Git. 39 | 40 | > Thank you! 41 | 42 | # How to make a change 43 | 44 | ## Process 45 | 46 | The typical process is to base your work on the `master` branch. The `master` branch must always contain a stable 47 | version of the project. It is possible to make changes by basing your work on other branches but the source 48 | of truth is `master`. If you want to synchronize with other people on other branches, feel free to. 49 | 50 | The process is: 51 | 52 | 1. (optional) Open an issue and discuss what you want to do. This is optional but highly recommended. If you 53 | don’t open an issue first and work on something that is not in the scope of the project, or already being 54 | made by someone else, you’ll be working for nothing. Also, keep in mind that if your change doesn’t refer to an 55 | existing issue, I will be wondering what is the context of your change. So prepare to be asked about the motivation 56 | and need behind your changes — it’s greatly appreciated if the commit messages, code and PR’s content already 57 | contains this information so that people don’t have to ask. 58 | 2. Fork the project. 59 | 3. Create a branch starting from `master` – or the branch you need to work on. Even though this is not really enforced, 60 | you’re advised to name your branch according to the _Git Flow_ naming convention: 61 | - `fix/your-bug-here`: if you’re fixing a bug, name your branch. 62 | - `feature/new-feature-here`: if you’re adding some work. 63 | - Free for anything else. 64 | - The special `release/*` branch is used to either back-port changes from newer versions to previous 65 | versions, or to release new versions by updating `Cargo.toml` files, changelogs, etc. Normally, contributors should 66 | never have to worry about this kind of brach as their creations is often triggered when wanting to make a release. 67 | 4. Make some commits! 68 | 5. Once you’re ready, open a Pull Request (PR) to merge your work on the target branch. For instance, open a PR for 69 | `master <- feature/something-new`. 70 | 6. (optional) Ask someone to review your code in the UI. Normally, I’m pretty reactive to notifications but it never 71 | hurts to ask for a review. 72 | 7. Discussion and peer-review. 73 | 8. Once the CI is all green, someone (likely me [@phaazon]) will merge your code and close your PR. 74 | 9. Feel free to delete your branch. 75 | 76 | # Conventions 77 | 78 | ## Coding 79 | 80 | Coding conventions are enforced by `rustfmt`. You are expected to provide code that is formatted by `rustfmt` 81 | with the top-level `rustfmt.toml` file. 82 | 83 | Coding convention is enforced in the Continuous Integration pipeline. If you want your work to be 84 | mergeable, format your code. 85 | 86 | > Note: please do not format your code in a separate, standalone commit. This way of doing is 87 | > considered as a bad practice as the commit will not contain _anything_ useful (but code 88 | > reformatted). Please format all your commits. You can use various tools in your editor to do so, 89 | > such as [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer). 90 | 91 | ## Git 92 | 93 | ### Git message 94 | 95 | Please format your git messages like so: 96 | 97 | > [project(s)] Starting with an uppercase letter, ending with a dot. #343 98 | > 99 | > The #343 after the dot is appreciated to link to issues. Feel free to add, like this message, more context 100 | > and/or precision to your git message. You don’t have to put it in the first line of the commit message, 101 | > but if you are fixing a bug or implementing a feature thas has an issue linked, please reference it, so 102 | > that it is easier to generate changelogs when reading the git log. 103 | 104 | The `[project(s)]` header is mandatory if your commit has changes in any of the crates handled by this repository. 105 | If the repository you contribute to has a single project in it, you can omit the `[project(s)]` part. If you make 106 | a change that is cross-crate, feel free to separate them with commas, like `[crate_a, crate_b]`. If 107 | you make a change that touches all the crates, you can use `[all]`. If you change something that is not related 108 | to a crate, like the front README, CONTRIBUTING file, CI setup, top-level `Cargo.toml`, etc., then you can omit 109 | this header. 110 | 111 | **I’m very strict on git messages as I use them to write `CHANGELOG.md` files. Don’t be surprised if I ask you 112 | to edit a commit message. :)** 113 | 114 | ### Commit atomicity 115 | 116 | Your commits should be as atomic as possible. That means that if you make a change that touches two different 117 | concepts / has different scopes, most of the time, you want two commits – for instance one commit for the backend crate 118 | and one commit for the interface crate. There are exceptions, so this is not an absolute rule, but take some time 119 | thinking about whether you should split your commits or not. Commits which add a feature / fix a bug _and_ add tests at 120 | the same time are fine. 121 | 122 | However, here’s a non-comprehensive list of commits considered bad and that will be refused: 123 | 124 | - **Formatting, refactoring, cleaning, linting code in a PR that is not strictly about formatting**. If you open a PR to 125 | fix a bug, implement a feature, change configuration, add metadata to the CI, etc. — pretty much anything — but you 126 | also format some old code that has nothing to do with your PR, apply a linter’s suggestions (such as `clippy`), remove 127 | old code, etc., then I will refuse your commit(s) and ask you to edit your PR. 128 | - **Too atomic commits**. If two commits are logically connected to one another and are small, it’s likely that you want 129 | to merge them as a single commit — unless they work on too different parts of your code. This is a bit subjective 130 | topic, so I won’t be _too picky_ about it, but if I judge that you should split a commit into two or fixup two commits, 131 | please don’t take it too personal. :) 132 | 133 | If you don’t know how to write your commits in an atomic maneer, think about how one would revert your commits if 134 | something bad happens with your changes — like a big breaking change we need to roll back from very quickly. If your 135 | commits are not atomic enough, rolling them back will also roll back code that has nothing to do with your changes. 136 | 137 | ### Hygiene 138 | 139 | When working on a fix or a feature, it’s very likely that you will periodically need to update your branch 140 | with the `master` branch. **Do not use merge commits**, as your contributions will be refused if you have 141 | merge commits in them. The only case where merge commits are accepted is when you work with someone else 142 | and are required to merge another branch into your feature branch (and even then, it is even advised to 143 | simply rebase). If you want to synchronize your branch with `master`, please use: 144 | 145 | ``` 146 | git switch 147 | git fetch origin --prune 148 | git rebase origin/master 149 | ``` 150 | 151 | # Release process 152 | 153 | ## Overall process 154 | 155 | Releases occur at arbitrary rates. If something is considered urgent, it is most of the time released immediately 156 | after being merged and tested. Sometimes, several issues are being fixed at the same time (spanning on a few 157 | days at max). Those will be gathered inside a single update. 158 | 159 | Feature requests might be delayed a bit to be packed together as well but eventually get released, even if 160 | they’re small. Getting your PR merged means it will be released _soon_, but depending on the urgency of your changes, 161 | it might take a few minutes to a few days. 162 | 163 | ## Changelogs update 164 | 165 | `CHANGELOG.md` files must be updated **before any release**. Especially, they must contain: 166 | 167 | - The version of the release. 168 | - The date of the release. 169 | - How to migrate from a minor to the next major. 170 | - Everything that a release has introduced, such as major, minor and patch changes. 171 | 172 | Because I don’t ask people to maintain changelogs, I have a high esteem of people knowing how to use Git and create 173 | correct commits. Be advised that I will refuse any commit that prevents me from writing the changelog correctly. 174 | 175 | ## Git tag 176 | 177 | Once a new release occurs, a Git tag is created. Git tags are formatted regarding the project they refer to, if several 178 | projects are present in the repository. If only one project is present, tags will refer to this project by the same 179 | naming scheme anyway: 180 | 181 | > -X.Y.Z 182 | 183 | Where `X` is the _major version_, `Y` is the _minor version_ and `Z` is the _patch version_. For instance 184 | `project-0.37.1` is a valid Git tag, so is `project-derive-0.5.3`. 185 | 186 | A special kind of tag is also possible: 187 | 188 | > -X.Y.Z-rc.W 189 | 190 | Where `W` is a number starting from `1` and incrementing. This format is for _release candidates_ and occurs 191 | when a new version (most of the time a major one) is to be released but more feedback is required. 192 | 193 | Crates are pushed to [crates.io](https://crates.io) and tagged on Git manually (no CD involved). 194 | 195 | # Support and donation 196 | 197 | This project is a _free and open-source_ project. It has no financial motivation nor support. I 198 | ([@phaazon]) would like to make it very clear that: 199 | 200 | - Sponsorship is not available. You cannot pay me to make me do things for you. That includes issues reports, 201 | features requests and such. 202 | - If you still want to donate because you like the project and think I should be rewarded, you are free to 203 | give whatever you want. 204 | - However, keep in mind that donating doesn’t unlock any privilege people who don’t donate wouldn’t already 205 | have. This is very important as it would bias priorities. Donations must remain anonymous. 206 | - For this reason, no _sponsor badge_ will be shown, as it would distinguish people who donate from those 207 | who don’t. This is a _free and open-source_ project, everybody is welcome to contribute, with or without 208 | money. 209 | 210 | [@phaazon]: https://github.com/phaazon 211 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "shades", 4 | "shades-edsl" 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Dimitri Sabadie 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Dimitri Sabadie nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shades, a shader EDSL in Rust 2 | 3 | ![](./docs/imgs/example.png) 4 | 5 | This crate provides an [EDSL] to build [shaders], leveraging the Rust compiler (`rustc`) and its type system to ensure 6 | soundness and typing. Because shaders are written in Rust, this crate is completely language agnostic: it can in theory 7 | target any shading language – the current tier-1 language being [GLSL]. The EDSL allows to statically type shaders 8 | while still generating the actual shading code at runtime. 9 | 10 | 11 | 12 | * [Motivation](#motivation) 13 | * [Influences](#influences) 14 | * [Why you would love this](#why-you-would-love-this) 15 | * [Why you wouldn’t love this](#why-you-wouldnt-love-this) 16 | 17 | 18 | 19 | ## Motivation 20 | 21 | In typical graphics libraries and engines, shaders are _opaque strings_ – either hard-coded in the program, read from 22 | a file at runtime, constructed via fragments of strings concatenated with each others, etc. The strings are passed to 23 | the graphics drivers, which will _compile_ and _link_ the code at runtime. It is the responsibility of the runtime 24 | (i.e. the graphics library, engine or the application) to check for errors and react correctly. Shading languages can 25 | also be compiled _off-line_, and their bytecode is then used at runtime (c.f. SPIR-V). 26 | 27 | For a lot of people, this has proven okay for decades and even allowed _live coding_: because the shading code is 28 | loaded at runtime, it is possible to re-load, re-compile and re-link it every time a change happens. However, this comes 29 | with a non-negligible drawbacks: 30 | 31 | - The shading code is often checked either at runtime. In this case, ill-written shaders won’t be visible by 32 | programmers until the runtime is executed and the GPU driver refuses the shading code. 33 | - When compiled off-line are transpiled to bytecode, extra specialized tooling is required (such as an external program, 34 | a language extension, etc.). 35 | - Writing shaders imply learning a new language. The most widespread shading language is [GLSL] but others exist, 36 | meaning that people will have to learn specialized languages and, most of the time, weaker compilation systems. For 37 | instance, [GLSL] doesn’t have anything natively to include other [GLSL] files and it’s an old C-like language. 38 | - Even though the appeal of using a language in a dynamic way can seem appealing, going from a dynamic language and 39 | using it in a statically manner is not an easy task. However, going the other way around (from a static to dynamic) 40 | is much much simpler. In other terms: it is possible to live-reload a compiled language with the help of low-level 41 | system primitives, such as `dlopen`, `dlsym`, etc. It’s more work but it’s possible. And 42 | [Rust can do it too](https://crates.io/crates/libloading). 43 | 44 | The author ([@phaazon]) of this crate thinks that shading code is still code, and that it should be treated as such. 45 | It’s easy to see the power of live-coding / reloading, but it’s more important to provide a shading code that is 46 | statically proven sound and with less bugs that without the static check. Also, as stated above, using a compiled 47 | approach doesn’t prevent from writing a relocatable object, compiled isolated and reload this object, providing roughly 48 | the same functionality as live-coding. 49 | 50 | > Important note: this crate **does its best** to catch semantic bugs at compile-time via `rustc`. However, it might 51 | > still lack opportunities to catch all semantic bugs. If you find such a case, please feel free to open an issue to as 52 | > that is considered a bug / regression. 53 | 54 | Another important point is the choice of using an EDSL. Some people would argue that Rust has other interesting and 55 | powerful ways to achieve the same goal. It is important to notice that this crate doesn’t provide a compiler to compile 56 | Rust code to a shading language. Instead, it provides a Rust crate that will still generate the shading code at runtime. 57 | Other alternatives would be using a [proc-macro]. Several crates who do this: 58 | 59 | - You can use the [glsl] and [glsl-quasiquote] crates. The first one is a parser for GLSL and the second one allows you 60 | to write GLSL in a quasi-quoter (`glsl! { /* here */ }`) and get it compiled and check at runtime. It’s still 61 | [GLSL], though, and the possibilities of runtime combinations are much less than an EDSL. 62 | - You can use the [rust-gpu] project. It’s a similar project but they use a proc-macro, compiling Rust code 63 | representing GPU code. It requires a specific toolchain and doesn’t operate at the same level of this crate — it can 64 | even compile a large part of the `core` library. 65 | 66 | ### Influences 67 | 68 | - [blaze-html], a [Haskell] [EDSL] to build HTML in [Haskell]. 69 | - [selda], a [Haskell] [EDSL] to build SQL queries and execute them without writing SQL strings. This current crate is 70 | very similar in the approach. 71 | 72 | ## Why you would love this 73 | 74 | If you like type systems, languages and basically hacking compilers (writing code for your compiler to generate the 75 | runtime code!), then it’s likely you’ll like this crate. Among all the features you will find: 76 | 77 | - Use vanilla Rust. Because this crate is language-agnostic, the whole thing you need to know to get started is to 78 | write Rust. You don’t have to learn [GLSL] to use this crate — even though you still need to understand the concept 79 | of shaders, what they are, how they work, etc. But the _encoding of those concepts_ is now encapsulated by a native 80 | Rust crate. 81 | - Types used to represent shading types are basic and native Rust types, such as `bool`, `f32` or `[T; N]`. 82 | - Write a more functional code rather than imperative code. For instance, a _vertex shader_ in this crate is basically 83 | a function taking an object of type `Vertex` and returning another object, that will be passed to the next stage. 84 | - Catch semantic bugs within `rustc`. For instance, assigning a `bool` to a `f32` in your shader code will trigger a 85 | `rustc` error. 86 | - Make some code impossible to write. For instance, you will not be able to use in a _vertex shader_ expressions only 87 | valid in the context of a _fragment shader_, as this is not possible by their own definitions. 88 | - Extend and add more items to famous shading languages. For instance, [GLSL] doesn’t have a `π` constant. This 89 | situation is fixed so you will never have to write `π` decimals by yourself anymore. 90 | - Because you write Rust, benefit from all the language type candies, composability, extensibility and soundness. 91 | - An experimental _monadic_ experience behind a _feature-gate_. This allows to write shaders by using the [do-notation] 92 | crate and remove a lot of boilerplate for you, making _scopes_ and _shader scopes_ hidden for you, making it feel 93 | like writing magic shading code. 94 | 95 | ## Why you wouldn’t love this 96 | 97 | The crate is, as of nowadays, still very experimental. Here’s a list of things you might dislike about the crate: 98 | 99 | - The current verbosity is non-acceptable. Most lambdas you’ll have to use require you to annotate their arguments, 100 | even though those are clearly guessable. This situation should be addressed as soon as possible, but people has to 101 | know that the current situation implies lots of type ascriptions. 102 | - Some people would argue that writing [GLSL] is much faster and simpler, and they would be right. However, you would 103 | need to learn [GLSL] in the first place; you wouldn’t be able to target SPIR-V; you wouldn’t have a solution to the 104 | static typing problem; etc. 105 | - In the case of a runtime compilation / linking failure of your shading code, debugging it might be challenging, as 106 | all the identifiers (with a few exceptions) are generated for you. It’ll make it harder to understand the generated 107 | code. 108 | - Some concepts, especially control-flow statements, look a bit weird. For instance, a `for` loop in [GLSL] is written 109 | with a much more convoluted way with this crate. The generated code is the same, but it is correctly more verbose via 110 | this crate. 111 | 112 | [@phaazon]: https://github.com/phaazon 113 | [EDSL]: https://en.wikipedia.org/wiki/Domain-specific_language#External_and_Embedded_Domain_Specific_Languages 114 | [shaders]: https://en.wikipedia.org/wiki/Shader 115 | [GLSL]: https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf 116 | [Haskell]: https://www.haskell.org 117 | [blaze-html]: http://hackage.haskell.org/package/blaze-html 118 | [selda]: http://hackage.haskell.org/package/selda 119 | [proc-macro]: https://doc.rust-lang.org/reference/procedural-macros.html 120 | [rust-gpu]: https://github.com/EmbarkStudios/rust-gpu 121 | [do-notation]: https://crates.io/crates/do-notation 122 | -------------------------------------------------------------------------------- /docs/edsl-proc-macro.md: -------------------------------------------------------------------------------- 1 | # EDSL procedural macro 2 | 3 | This is a design document trying to explore how to ease building EDSL nodes by leveraging the power of procedural 4 | macros. Especially, this document explores the possibility to annotate and automatically lift expressions and 5 | statement, removing a lot of abstractions that currently exists just to please `rustc`. 6 | 7 | 8 | 9 | * [Summary](#summary) 10 | * [Context & problems](#context--problems) 11 | * [Solution](#solution) 12 | * [Automatic scope prefixing](#automatic-scope-prefixing) 13 | * [Automatic literals lifting and variables transformation](#automatic-literals-lifting-and-variables-transformation) 14 | * [Sharable blocks](#sharable-blocks) 15 | * [The EDSL](#the-edsl) 16 | 17 | 18 | 19 | # Summary 20 | 21 | # Context & problems 22 | 23 | At the time of writing this document, the design of `shades` works but is fairly difficult to work with. For instance, 24 | creating a new function requires to either use a nightly compiler (to be able to use the nightly implementors of 25 | `FnOnce`, `Fn` and `FnMut`), or to use an ugly `.call((expr0, expr1, …))` syntax. Another problem is the presence of 26 | complex traits, such has `FunBuilder`. 27 | 28 | The main big problems are: 29 | 30 | - Type ascriptions, which are required pretty much everywhere. 31 | - “Magic” macro to lift literals as expressions, such as in `lit!(1) + 2`. 32 | - Side-effects (setting a variable) requires using a function (`.set()`), because there is no way to override the 33 | assignment operator in Rust. 34 | - Everything that is a scope must be prefixed with the scope object (most of the time, `s.`), and the scope type must be 35 | ascripted as well. 36 | 37 | # Solution 38 | 39 | In order to resolve all the problems from above, we are going to analysis each point and suggest a solution for each of 40 | them. 41 | 42 | ## Automatic scope prefixing 43 | 44 | The current code is problematic: 45 | 46 | ```rust 47 | shader.main_fun(|s: &mut Scope<()>| { 48 | let x = s.var(1.); 49 | 50 | // … 51 | ``` 52 | 53 | Here, we can see that creating the `main` function requires to create an `s: Scope<()>`, with type ascription, and we 54 | have to use that scope whenever we want to do anything with it. Instead, something that would be nicer would be to 55 | completely hide the scope behind the scene, in a monadic way: 56 | 57 | ```rust 58 | // … 59 | let x = 1.; 60 | ``` 61 | 62 | ## Automatic literals lifting and variables transformation 63 | 64 | In the current code, we have a lot of `.clone()` and `.into()` in order to convert from `Var` to `Expr`. On the same 65 | idea, we also have a lot of weird `lit!(x) + y`. We propose to simply completely remove that and replace any literal 66 | value with the following: 67 | 68 | ```rust 69 | Expr::from(lit) 70 | ``` 71 | 72 | For instance, the following: 73 | 74 | ```rust 75 | 1 + 2 76 | ``` 77 | 78 | will then be replaced by: 79 | 80 | ```rust 81 | Expr::from(1) + Expr::from(2) 82 | ``` 83 | 84 | Similarly, downcasting a `Var` to an `Expr` will always be done via `Expr::from`. Whenever an expression is expected, 85 | `Expr::from` will be used. So assuming `v` is a `Var`, the following: 86 | 87 | ```rust 88 | let x = v + 3; 89 | ``` 90 | 91 | will be replaced by: 92 | 93 | ```rust 94 | let x = Expr::from(&v) + Expr::from(3); 95 | ``` 96 | 97 | ## Sharable blocks 98 | 99 | One of the main pain points of GLSL is that it’s impossible to share things. For instance, if we want to set `PI` and 100 | use it by importing it, we have to basically copy a string line and paste it whenever it’s needed. It’s a very old way 101 | of doing and it doesn’t scale / is error-prone. 102 | 103 | Instead, we are going to support importing symbols inside shader stages. In order to do so, we will hijack `use` 104 | statements. 105 | 106 | ## The EDSL 107 | 108 | The EDSL will be written as a procedural macro, and we will have a couple of them. We want to be able to write a _shader 109 | stage_ using the macro, but we also want to be able to write code that can be `use`d by other pieces of code. So we will 110 | need two proc-macros: 111 | 112 | - `shades!`, that will create a shader stage. 113 | - `shades_def!`, that will create a new definition that can be used by other `shades_def!` or `shades!` blocks. 114 | -------------------------------------------------------------------------------- /docs/imgs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadronized/shades/f503d1d7873b55f75b68aa01fb4fc74f37e493a2/docs/imgs/example.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | fn_args_layout = "Tall" 4 | force_explicit_abi = true 5 | hard_tabs = false 6 | max_width = 100 7 | merge_derives = true 8 | newline_style = "Unix" 9 | remove_nested_parens = true 10 | reorder_imports = true 11 | reorder_modules = true 12 | tab_spaces = 2 13 | use_field_init_shorthand = true 14 | use_small_heuristics = "Default" 15 | use_try_shorthand = true 16 | -------------------------------------------------------------------------------- /shades-edsl/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1 2 | 3 | - Initial revision. 4 | -------------------------------------------------------------------------------- /shades-edsl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shades-edsl" 3 | version = "0.1.0" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "An EDSL for shading languages" 7 | keywords = ["edsl", "shader", "glsl", "spir-v"] 8 | categories = ["graphics"] 9 | homepage = "https://github.com/phaazon/shades" 10 | repository = "https://github.com/phaazon/shades" 11 | documentation = "https://docs.rs/shades" 12 | readme = "../README.md" 13 | edition = "2018" 14 | 15 | [badges] 16 | maintenance = { status = "actively-developed" } 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [dependencies] 22 | proc-macro2 = "1" 23 | quote = "1" 24 | syn = { version = "1", features = ["extra-traits", "full", "visit-mut"] } 25 | -------------------------------------------------------------------------------- /shades-edsl/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod syntax; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::ToTokens; 5 | use syn::parse_macro_input; 6 | 7 | use crate::syntax::StageDecl; 8 | 9 | #[proc_macro] 10 | pub fn shades(tokens: TokenStream) -> TokenStream { 11 | let mut stage = parse_macro_input!(tokens as StageDecl); 12 | 13 | stage.mutate(); 14 | 15 | stage.into_token_stream().into() 16 | } 17 | -------------------------------------------------------------------------------- /shades-edsl/src/syntax.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens}; 2 | use syn::{ 3 | braced, parenthesized, 4 | parse::Parse, 5 | parse_quote, 6 | punctuated::Punctuated, 7 | token::{Brace, Paren}, 8 | visit_mut::VisitMut, 9 | BinOp, Expr, Ident, Token, Type, 10 | }; 11 | 12 | /// A stage declaration with its environment. 13 | #[derive(Debug)] 14 | pub struct StageDecl { 15 | stage_ty: Type, 16 | _left_or: Token![|], 17 | input: FnArgItem, 18 | _comma_input_token: Token![,], 19 | output: FnArgItem, 20 | _comma_output_token: Token![,], 21 | env: FnArgItem, 22 | _right_or: Token![|], 23 | _brace_token: Brace, 24 | stage_item: StageItem, 25 | } 26 | 27 | impl StageDecl { 28 | pub fn mutate(&mut self) { 29 | self.stage_item.mutate() 30 | } 31 | } 32 | 33 | impl ToTokens for StageDecl { 34 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 35 | let stage_ty = &self.stage_ty; 36 | let mut input = self.input.clone(); 37 | let mut output = self.output.clone(); 38 | let mut env = self.env.clone(); 39 | let stage = &self.stage_item; 40 | 41 | let in_ty = input.ty.clone(); 42 | let out_ty = output.ty.clone(); 43 | let env_ty = env.ty.clone(); 44 | input.ty = parse_quote! { <#stage_ty as shades::stage::ShaderModule<#in_ty, #out_ty>>::Inputs }; 45 | output.ty = 46 | parse_quote! { <#stage_ty as shades::stage::ShaderModule<#in_ty, #out_ty>>::Outputs }; 47 | env.ty = parse_quote! { <#env_ty as shades::env::Environment>::Env }; 48 | 49 | let q = quote! { 50 | shades::stage::ModBuilder::<#stage_ty, #in_ty, #out_ty, #env_ty>::new_stage(|mut __builder, #input, #output, #env| { 51 | #stage 52 | }) 53 | }; 54 | 55 | q.to_tokens(tokens); 56 | } 57 | } 58 | 59 | impl Parse for StageDecl { 60 | fn parse(input: syn::parse::ParseStream) -> Result { 61 | let stage_ty = input.parse()?; 62 | let left_or = input.parse()?; 63 | let input_ = input.parse()?; 64 | let comma_input_token = input.parse()?; 65 | let output = input.parse()?; 66 | let comma_output_token = input.parse()?; 67 | let env = input.parse()?; 68 | let right_or = input.parse()?; 69 | 70 | let stage_input; 71 | let brace_token = braced!(stage_input in input); 72 | let stage_item = stage_input.parse()?; 73 | 74 | Ok(Self { 75 | stage_ty, 76 | _left_or: left_or, 77 | input: input_, 78 | _comma_input_token: comma_input_token, 79 | output, 80 | _comma_output_token: comma_output_token, 81 | env, 82 | _right_or: right_or, 83 | _brace_token: brace_token, 84 | stage_item, 85 | }) 86 | } 87 | } 88 | 89 | /// A stage. 90 | /// 91 | /// A stage contains global declarations. 92 | #[derive(Debug)] 93 | pub struct StageItem { 94 | glob_decl: Vec, 95 | } 96 | 97 | impl StageItem { 98 | fn mutate(&mut self) { 99 | for decl in &mut self.glob_decl { 100 | decl.mutate(); 101 | } 102 | } 103 | } 104 | 105 | impl ToTokens for StageItem { 106 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 107 | let glob_decl = self.glob_decl.iter(); 108 | let q = quote! { #(#glob_decl)* }; 109 | 110 | q.to_tokens(tokens); 111 | } 112 | } 113 | 114 | impl Parse for StageItem { 115 | fn parse(input: syn::parse::ParseStream) -> Result { 116 | let mut glob_decl = Vec::new(); 117 | 118 | loop { 119 | let lookahead = input.lookahead1(); 120 | 121 | if lookahead.peek(Token![const]) { 122 | glob_decl.push(ShaderDeclItem::Const(input.parse()?)); 123 | } else if lookahead.peek(Token![fn]) { 124 | glob_decl.push(ShaderDeclItem::FunDef(input.parse()?)); 125 | } else { 126 | break Ok(StageItem { glob_decl }); 127 | } 128 | } 129 | } 130 | } 131 | 132 | #[derive(Debug)] 133 | pub enum ShaderDeclItem { 134 | Const(ConstItem), 135 | FunDef(FunDefItem), 136 | } 137 | 138 | impl ShaderDeclItem { 139 | fn mutate(&mut self) { 140 | match self { 141 | ShaderDeclItem::Const(const_item) => const_item.mutate(), 142 | ShaderDeclItem::FunDef(fun_def_item) => fun_def_item.mutate(), 143 | } 144 | } 145 | } 146 | 147 | impl ToTokens for ShaderDeclItem { 148 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 149 | let q = match self { 150 | ShaderDeclItem::Const(const_item) => quote! { #const_item }, 151 | ShaderDeclItem::FunDef(fundef_item) => quote! { #fundef_item }, 152 | }; 153 | 154 | q.to_tokens(tokens); 155 | } 156 | } 157 | 158 | #[derive(Debug)] 159 | pub struct ConstItem { 160 | _const_token: Token![const], 161 | ident: Ident, 162 | _colon_token: Token![:], 163 | ty: Type, 164 | _assign_token: Token![=], 165 | expr: Expr, 166 | _semi_token: Token![;], 167 | } 168 | 169 | impl ConstItem { 170 | fn mutate(&mut self) { 171 | ExprVisitor.visit_type_mut(&mut self.ty); 172 | ExprVisitor.visit_expr_mut(&mut self.expr); 173 | } 174 | } 175 | 176 | impl ToTokens for ConstItem { 177 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 178 | let ident = &self.ident; 179 | let ty = &self.ty; 180 | let expr = &self.expr; 181 | let q = quote! { 182 | let #ident: #ty = __builder.constant(#expr); 183 | }; 184 | 185 | q.to_tokens(tokens); 186 | } 187 | } 188 | 189 | impl Parse for ConstItem { 190 | fn parse(input: syn::parse::ParseStream) -> Result { 191 | let const_token = input.parse()?; 192 | let ident = input.parse()?; 193 | let colon_token = input.parse()?; 194 | let ty = input.parse()?; 195 | let assign_token = input.parse()?; 196 | let expr = input.parse()?; 197 | let semi_token = input.parse()?; 198 | 199 | Ok(Self { 200 | _const_token: const_token, 201 | ident, 202 | _colon_token: colon_token, 203 | ty, 204 | _assign_token: assign_token, 205 | expr, 206 | _semi_token: semi_token, 207 | }) 208 | } 209 | } 210 | 211 | #[derive(Clone, Debug)] 212 | pub struct FnArgItem { 213 | ident: Ident, 214 | _colon_token: Token![:], 215 | pound_token: Option, 216 | ty: Type, 217 | } 218 | 219 | impl FnArgItem { 220 | fn mutate(&mut self) { 221 | if self.pound_token.is_none() { 222 | // if we don’t have a #ty but just ty, we lift the type in Expr<#ty> 223 | ExprVisitor.visit_type_mut(&mut self.ty); 224 | }; 225 | } 226 | } 227 | 228 | impl ToTokens for FnArgItem { 229 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 230 | let ident = &self.ident; 231 | let ty = &self.ty; 232 | let q = quote! { 233 | #ident: #ty 234 | }; 235 | 236 | q.to_tokens(tokens); 237 | } 238 | } 239 | 240 | impl Parse for FnArgItem { 241 | fn parse(input: syn::parse::ParseStream) -> Result { 242 | let ident = input.parse()?; 243 | let colon_token = input.parse()?; 244 | let pound_token = input.parse()?; 245 | let ty = input.parse()?; 246 | 247 | Ok(Self { 248 | ident, 249 | _colon_token: colon_token, 250 | pound_token, 251 | ty, 252 | }) 253 | } 254 | } 255 | 256 | #[derive(Debug)] 257 | pub struct FunDefItem { 258 | _fn_token: Token![fn], 259 | name: Ident, 260 | _paren_token: Paren, 261 | args: Punctuated, 262 | ret_ty: Option<(Token![->], Type)>, 263 | _brace_token: Brace, 264 | body: ScopeInstrItems, 265 | } 266 | 267 | impl FunDefItem { 268 | fn mutate(&mut self) { 269 | for arg in self.args.iter_mut() { 270 | arg.mutate(); 271 | } 272 | 273 | self.body.mutate(); 274 | } 275 | } 276 | 277 | impl ToTokens for FunDefItem { 278 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 279 | let name = &self.name; 280 | let body = &self.body; 281 | 282 | if name.to_string() == "main" { 283 | let q = quote! { 284 | // scope everything to prevent leaking them out 285 | { 286 | // function scope 287 | let mut __scope: shades::scope::Scope<()> = shades::scope::Scope::new(0); 288 | 289 | // roll down the statements 290 | #body 291 | 292 | // create the function definition 293 | use shades::erased::Erased as _; 294 | let erased = shades::fun::ErasedFun::new(Vec::new(), None, __scope.to_erased()); 295 | let fundef = shades::fun::FunDef::<(), ()>::new(erased); 296 | __builder.main_fun(fundef) 297 | } 298 | }; 299 | 300 | q.to_tokens(tokens); 301 | return; 302 | } 303 | 304 | let args_ty = self.args.iter().map(|arg| &arg.ty); 305 | 306 | // function argument types 307 | let fn_args_ty = self.args.iter().map(|arg| { 308 | let ty = &arg.ty; 309 | quote! { <#ty as shades::types::ToType>::ty() } 310 | }); 311 | 312 | // function argument expression declarations 313 | let args_expr_decls = self.args.iter().enumerate().map(|(k, arg)| { 314 | let ty = &arg.ty; 315 | let ident = &arg.ident; 316 | quote! { let #ident: #ty = shades::expr::Expr::new_fun_arg(#k as u16); } 317 | }); 318 | 319 | // function return type 320 | let ret_ty = &self.ret_ty; 321 | let (quoted_ret_ty, real_ret_ty) = match ret_ty { 322 | None => (quote! { None }, quote! { () }), 323 | 324 | Some((_, ret_ty)) => { 325 | if let Type::Tuple(..) = ret_ty { 326 | (quote! { None }, quote! { () }) 327 | } else { 328 | ( 329 | quote! { Some(<#ret_ty as shades::types::ToType>::ty()) }, 330 | quote! { shades::expr::Expr<#ret_ty> }, 331 | ) 332 | } 333 | } 334 | }; 335 | 336 | let q = quote! { 337 | // scope everything to prevent leaking them out 338 | let #name = { 339 | // function scope 340 | let mut __scope: shades::scope::Scope<#real_ret_ty> = shades::scope::Scope::new(0); 341 | 342 | // create the function argument expressions so that #body can reference them 343 | #(#args_expr_decls)* 344 | 345 | // roll down the statements 346 | #body 347 | 348 | // create the function definition 349 | use shades::erased::Erased as _; 350 | let erased = shades::fun::ErasedFun::new(vec![#(#fn_args_ty),*], #quoted_ret_ty, __scope.to_erased()); 351 | let fundef = shades::fun::FunDef::<#real_ret_ty, (#(#args_ty),*)>::new(erased); 352 | __builder.fun(fundef) 353 | }; 354 | }; 355 | 356 | q.to_tokens(tokens); 357 | } 358 | } 359 | 360 | impl Parse for FunDefItem { 361 | fn parse(input: syn::parse::ParseStream) -> Result { 362 | let fn_token = input.parse()?; 363 | let name = input.parse()?; 364 | 365 | let args_input; 366 | let paren_token = parenthesized!(args_input in input); 367 | let args = Punctuated::parse_terminated(&args_input)?; 368 | 369 | let ret_ty = if input.peek(Token![->]) { 370 | let arrow_token = input.parse()?; 371 | let ret_ty = input.parse()?; 372 | Some((arrow_token, ret_ty)) 373 | } else { 374 | None 375 | }; 376 | 377 | let body_input; 378 | let brace_token = braced!(body_input in input); 379 | let body = body_input.parse()?; 380 | 381 | Ok(Self { 382 | _fn_token: fn_token, 383 | name, 384 | _paren_token: paren_token, 385 | args, 386 | ret_ty, 387 | _brace_token: brace_token, 388 | body, 389 | }) 390 | } 391 | } 392 | 393 | #[derive(Debug)] 394 | pub struct ScopeInstrItems { 395 | items: Vec, 396 | } 397 | 398 | impl ScopeInstrItems { 399 | fn mutate(&mut self) { 400 | for item in &mut self.items { 401 | item.mutate(); 402 | } 403 | } 404 | } 405 | 406 | impl ToTokens for ScopeInstrItems { 407 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 408 | let items = &self.items; 409 | let q = quote! { #(#items)*}; 410 | q.to_tokens(tokens); 411 | } 412 | } 413 | 414 | impl Parse for ScopeInstrItems { 415 | fn parse(input: syn::parse::ParseStream) -> Result { 416 | let mut items = Vec::new(); 417 | 418 | while !input.is_empty() { 419 | let lookahead = input.lookahead1(); 420 | 421 | let v = if lookahead.peek(Token![let]) { 422 | let v = input.parse()?; 423 | ScopeInstrItem::VarDecl(v) 424 | } else if lookahead.peek(Token![continue]) { 425 | let v = input.parse()?; 426 | ScopeInstrItem::Continue(v) 427 | } else if lookahead.peek(Token![break]) { 428 | let v = input.parse()?; 429 | ScopeInstrItem::Break(v) 430 | } else if lookahead.peek(Token![if]) { 431 | let v = input.parse()?; 432 | ScopeInstrItem::If(v) 433 | } else if lookahead.peek(Token![while]) { 434 | let v = input.parse()?; 435 | ScopeInstrItem::While(v) 436 | } else { 437 | // try to parse a mutate var first, otherwise fallback on return 438 | let input_ = input.fork(); 439 | if input_.parse::().is_ok() { 440 | // advance the original input 441 | let v = input.parse()?; 442 | ScopeInstrItem::MutateVar(v) 443 | } else { 444 | let v = input.parse()?; 445 | ScopeInstrItem::Return(v) 446 | } 447 | }; 448 | 449 | items.push(v); 450 | } 451 | 452 | Ok(Self { items }) 453 | } 454 | } 455 | 456 | #[derive(Debug)] 457 | pub enum ScopeInstrItem { 458 | VarDecl(VarDeclItem), 459 | Return(ReturnItem), 460 | Continue(ContinueItem), 461 | Break(BreakItem), 462 | If(IfItem), 463 | // For(ForItem), // TODO 464 | While(WhileItem), 465 | MutateVar(MutateVarItem), 466 | } 467 | 468 | impl ScopeInstrItem { 469 | fn mutate(&mut self) { 470 | match self { 471 | ScopeInstrItem::VarDecl(var_decl) => var_decl.mutate(), 472 | ScopeInstrItem::Return(ret) => ret.mutate(), 473 | ScopeInstrItem::If(if_) => if_.mutate(), 474 | ScopeInstrItem::While(while_) => while_.mutate(), 475 | ScopeInstrItem::MutateVar(mutate_var) => mutate_var.mutate(), 476 | _ => (), 477 | } 478 | } 479 | } 480 | 481 | impl ToTokens for ScopeInstrItem { 482 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 483 | let q = match self { 484 | ScopeInstrItem::VarDecl(decl) => { 485 | // FIXME: lift the whole expression 486 | let name = &decl.name; 487 | let expr = &decl.expr; 488 | 489 | if let Some((_, ty)) = &decl.ty { 490 | quote! { 491 | let #name: #ty = __scope.var(#expr); 492 | } 493 | } else { 494 | quote! { 495 | let #name = __scope.var(#expr); 496 | } 497 | } 498 | } 499 | 500 | ScopeInstrItem::Return(ret) => { 501 | let expr = ret.expr(); 502 | 503 | quote! { 504 | __scope.leave(#expr); 505 | } 506 | } 507 | 508 | ScopeInstrItem::Continue(_) => { 509 | quote! { 510 | __scope.loop_continue(); 511 | } 512 | } 513 | 514 | ScopeInstrItem::Break(_) => { 515 | quote! { 516 | __scope.loop_break(); 517 | } 518 | } 519 | 520 | ScopeInstrItem::If(if_item) => { 521 | quote! { #if_item } 522 | } 523 | 524 | ScopeInstrItem::While(while_item) => { 525 | let cond = &while_item.cond_expr; 526 | let body = &while_item.body; 527 | quote! { 528 | __scope.loop_while(#cond, |__scope| { 529 | #body 530 | }); 531 | } 532 | } 533 | 534 | ScopeInstrItem::MutateVar(mutate_var) => { 535 | quote! { 536 | #mutate_var; 537 | } 538 | } 539 | }; 540 | 541 | q.to_tokens(tokens); 542 | } 543 | } 544 | 545 | #[derive(Debug)] 546 | pub struct VarDeclItem { 547 | _let_token: Token![let], 548 | name: Ident, 549 | ty: Option<(Token![:], Type)>, 550 | _assign_token: Token![=], 551 | expr: Expr, 552 | _semi_token: Token![;], 553 | } 554 | 555 | impl VarDeclItem { 556 | fn mutate(&mut self) { 557 | if let Some((_, ref mut ty)) = self.ty { 558 | ExprVisitor.visit_type_mut(ty); 559 | } 560 | 561 | ExprVisitor.visit_expr_mut(&mut self.expr); 562 | } 563 | } 564 | 565 | impl Parse for VarDeclItem { 566 | fn parse(input: syn::parse::ParseStream) -> Result { 567 | let let_token = input.parse()?; 568 | let name = input.parse()?; 569 | 570 | let ty = if input.peek(Token![:]) { 571 | let colon_token = input.parse()?; 572 | let ty = input.parse()?; 573 | Some((colon_token, ty)) 574 | } else { 575 | None 576 | }; 577 | let assign_token = input.parse()?; 578 | let expr = input.parse()?; 579 | let semi_token = input.parse()?; 580 | 581 | Ok(Self { 582 | _let_token: let_token, 583 | name, 584 | ty, 585 | _assign_token: assign_token, 586 | expr, 587 | _semi_token: semi_token, 588 | }) 589 | } 590 | } 591 | 592 | // FIXME: this encoding is wrong; it should be either an expression, or return + expr + semi 593 | #[derive(Debug)] 594 | pub enum ReturnItem { 595 | Return { 596 | return_token: Option, 597 | expr: Expr, 598 | semi_token: Token![;], 599 | }, 600 | 601 | Implicit { 602 | expr: Expr, 603 | }, 604 | } 605 | 606 | impl ReturnItem { 607 | fn expr(&self) -> &Expr { 608 | match self { 609 | ReturnItem::Return { expr, .. } => expr, 610 | ReturnItem::Implicit { expr } => expr, 611 | } 612 | } 613 | fn mutate(&mut self) { 614 | match self { 615 | ReturnItem::Return { expr, .. } => ExprVisitor.visit_expr_mut(expr), 616 | ReturnItem::Implicit { expr } => ExprVisitor.visit_expr_mut(expr), 617 | } 618 | } 619 | } 620 | 621 | impl Parse for ReturnItem { 622 | fn parse(input: syn::parse::ParseStream) -> Result { 623 | if input.peek(Token![return]) { 624 | let return_token = input.parse()?; 625 | let expr = input.parse()?; 626 | let semi_token = input.parse()?; 627 | 628 | Ok(ReturnItem::Return { 629 | return_token, 630 | expr, 631 | semi_token, 632 | }) 633 | } else { 634 | let expr = input.parse()?; 635 | 636 | Ok(ReturnItem::Implicit { expr }) 637 | } 638 | } 639 | } 640 | 641 | #[derive(Debug)] 642 | pub struct ContinueItem { 643 | _continue_token: Token![continue], 644 | _semi_token: Token![;], 645 | } 646 | 647 | impl Parse for ContinueItem { 648 | fn parse(input: syn::parse::ParseStream) -> Result { 649 | let continue_token = input.parse()?; 650 | let semi_token = input.parse()?; 651 | Ok(Self { 652 | _continue_token: continue_token, 653 | _semi_token: semi_token, 654 | }) 655 | } 656 | } 657 | 658 | #[derive(Debug)] 659 | pub struct BreakItem { 660 | _break_token: Token![break], 661 | _semi_token: Token![;], 662 | } 663 | 664 | impl Parse for BreakItem { 665 | fn parse(input: syn::parse::ParseStream) -> Result { 666 | let break_token = input.parse()?; 667 | let semi_token = input.parse()?; 668 | Ok(Self { 669 | _break_token: break_token, 670 | _semi_token: semi_token, 671 | }) 672 | } 673 | } 674 | 675 | #[derive(Debug)] 676 | pub struct IfItem { 677 | _if_token: Token![if], 678 | _paren_token: Paren, 679 | cond_expr: Expr, 680 | _brace_token: Brace, 681 | body: ScopeInstrItems, 682 | else_item: Option, 683 | } 684 | 685 | impl IfItem { 686 | fn mutate(&mut self) { 687 | ExprVisitor.visit_expr_mut(&mut self.cond_expr); 688 | 689 | self.body.mutate(); 690 | 691 | if let Some(ref mut else_item) = self.else_item { 692 | else_item.mutate(); 693 | } 694 | } 695 | } 696 | 697 | impl IfItem { 698 | fn to_tokens_as_part_of_else(&self, tokens: &mut proc_macro2::TokenStream) { 699 | let cond = &self.cond_expr; 700 | let body = &self.body; 701 | 702 | let q = quote! { 703 | .or_else(#cond, |__scope| { 704 | #body 705 | }) 706 | }; 707 | 708 | q.to_tokens(tokens); 709 | 710 | match self.else_item { 711 | Some(ref else_item) => { 712 | let else_tail = &else_item.else_tail; 713 | quote! { #else_tail }.to_tokens(tokens); 714 | } 715 | 716 | None => { 717 | quote! { ; }.to_tokens(tokens); 718 | } 719 | } 720 | } 721 | } 722 | 723 | impl ToTokens for IfItem { 724 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 725 | let cond = &self.cond_expr; 726 | let body = &self.body; 727 | 728 | let q = quote! { 729 | __scope.when(#cond, |__scope| { 730 | #body 731 | }) }; 732 | 733 | q.to_tokens(tokens); 734 | 735 | match self.else_item { 736 | Some(ref else_item) => { 737 | let else_tail = &else_item.else_tail; 738 | quote! { #else_tail }.to_tokens(tokens); 739 | } 740 | 741 | None => { 742 | quote! { ; }.to_tokens(tokens); 743 | } 744 | } 745 | } 746 | } 747 | 748 | impl Parse for IfItem { 749 | fn parse(input: syn::parse::ParseStream) -> Result { 750 | let if_token = input.parse()?; 751 | 752 | let cond_input; 753 | let paren_token = parenthesized!(cond_input in input); 754 | let cond_expr = cond_input.parse()?; 755 | 756 | let body_input; 757 | let brace_token = braced!(body_input in input); 758 | let body = body_input.parse()?; 759 | 760 | let else_item = if input.peek(Token![else]) { 761 | Some(input.parse()?) 762 | } else { 763 | None 764 | }; 765 | 766 | Ok(Self { 767 | _if_token: if_token, 768 | _paren_token: paren_token, 769 | cond_expr, 770 | _brace_token: brace_token, 771 | body, 772 | else_item, 773 | }) 774 | } 775 | } 776 | 777 | #[derive(Debug)] 778 | pub struct ElseItem { 779 | _else_token: Token![else], 780 | else_tail: ElseTailItem, 781 | } 782 | 783 | impl ElseItem { 784 | fn mutate(&mut self) { 785 | self.else_tail.mutate(); 786 | } 787 | } 788 | 789 | impl Parse for ElseItem { 790 | fn parse(input: syn::parse::ParseStream) -> Result { 791 | let else_token = input.parse()?; 792 | let else_tail = input.parse()?; 793 | 794 | Ok(Self { 795 | _else_token: else_token, 796 | else_tail, 797 | }) 798 | } 799 | } 800 | 801 | #[derive(Debug)] 802 | pub enum ElseTailItem { 803 | If(Box), 804 | 805 | Else { 806 | brace_token: Brace, 807 | body: ScopeInstrItems, 808 | }, 809 | } 810 | 811 | impl ElseTailItem { 812 | fn mutate(&mut self) { 813 | match self { 814 | ElseTailItem::If(if_item) => if_item.mutate(), 815 | ElseTailItem::Else { body, .. } => body.mutate(), 816 | } 817 | } 818 | } 819 | 820 | impl ToTokens for ElseTailItem { 821 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 822 | match self { 823 | ElseTailItem::If(if_item) => { 824 | if_item.to_tokens_as_part_of_else(tokens); 825 | } 826 | 827 | ElseTailItem::Else { body, .. } => { 828 | let q = quote! { 829 | .or(|__scope| { #body }); 830 | }; 831 | 832 | q.to_tokens(tokens); 833 | } 834 | } 835 | } 836 | } 837 | 838 | impl Parse for ElseTailItem { 839 | fn parse(input: syn::parse::ParseStream) -> Result { 840 | let tail = if input.peek(Token![if]) { 841 | let if_item = input.parse()?; 842 | ElseTailItem::If(if_item) 843 | } else { 844 | let body_input; 845 | let brace_token = braced!(body_input in input); 846 | let body = body_input.parse()?; 847 | 848 | ElseTailItem::Else { brace_token, body } 849 | }; 850 | 851 | Ok(tail) 852 | } 853 | } 854 | 855 | #[derive(Debug)] 856 | pub struct WhileItem { 857 | _while_token: Token![while], 858 | cond_expr: Expr, 859 | _brace_token: Brace, 860 | body: ScopeInstrItems, 861 | } 862 | 863 | impl WhileItem { 864 | fn mutate(&mut self) { 865 | ExprVisitor.visit_expr_mut(&mut self.cond_expr); 866 | self.body.mutate(); 867 | } 868 | } 869 | 870 | impl Parse for WhileItem { 871 | fn parse(input: syn::parse::ParseStream) -> Result { 872 | let while_token = input.parse()?; 873 | 874 | let cond_expr = input.parse()?; 875 | 876 | let body_input; 877 | let brace_token = braced!(body_input in input); 878 | let body = body_input.parse()?; 879 | 880 | Ok(Self { 881 | _while_token: while_token, 882 | cond_expr, 883 | _brace_token: brace_token, 884 | body, 885 | }) 886 | } 887 | } 888 | 889 | #[derive(Debug)] 890 | pub enum MutateVarAssignToken { 891 | Assign(Token![=]), 892 | AssignBinOp(BinOp), 893 | } 894 | 895 | impl Parse for MutateVarAssignToken { 896 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 897 | if input.peek(Token![=]) { 898 | let token = input.parse()?; 899 | Ok(Self::Assign(token)) 900 | } else { 901 | let bin_op = input.parse()?; 902 | Ok(Self::AssignBinOp(bin_op)) 903 | } 904 | } 905 | } 906 | 907 | #[derive(Debug)] 908 | pub struct MutateVarItem { 909 | ident: Ident, 910 | assign_token: MutateVarAssignToken, 911 | expr: Expr, 912 | _semi_token: Token![;], 913 | } 914 | 915 | impl MutateVarItem { 916 | fn mutate(&mut self) { 917 | ExprVisitor.visit_expr_mut(&mut self.expr); 918 | } 919 | } 920 | 921 | impl ToTokens for MutateVarItem { 922 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 923 | let ident = &self.ident; 924 | let assign_token = &self.assign_token; 925 | let expr = &self.expr; 926 | 927 | let bin_op = match assign_token { 928 | MutateVarAssignToken::Assign(_) => quote! { None }, 929 | MutateVarAssignToken::AssignBinOp(bin_op) => match bin_op { 930 | syn::BinOp::AddEq(_) => quote! { Some(shades::scope::MutateBinOp::Add) }, 931 | syn::BinOp::SubEq(_) => quote! { Some(shades::scope::MutateBinOp::Sub) }, 932 | syn::BinOp::MulEq(_) => quote! { Some(shades::scope::MutateBinOp::Mul) }, 933 | syn::BinOp::DivEq(_) => quote! { Some(shades::scope::MutateBinOp::Div) }, 934 | syn::BinOp::RemEq(_) => quote! { Some(shades::scope::MutateBinOp::Rem) }, 935 | syn::BinOp::BitXorEq(_) => quote! { Some(shades::scope::MutateBinOp::Xor) }, 936 | syn::BinOp::BitAndEq(_) => quote! { Some(shades::scope::MutateBinOp::And) }, 937 | syn::BinOp::BitOrEq(_) => quote! { Some(shades::scope::MutateBinOp::Or) }, 938 | syn::BinOp::ShlEq(_) => quote! { Some(shades::scope::MutateBinOp::Shl) }, 939 | syn::BinOp::ShrEq(_) => quote! { Some(shades::scope::MutateBinOp::Shr) }, 940 | _ => quote! { compile_error!("expecting +=, -=, *=, /=, %=, ^=, &=, |=, <<= or >>=") }, 941 | }, 942 | }; 943 | let q = quote! { 944 | __scope.set(#ident.clone(), #bin_op, shades::expr::Expr::from(#expr)) 945 | }; 946 | 947 | q.to_tokens(tokens); 948 | } 949 | } 950 | 951 | impl Parse for MutateVarItem { 952 | fn parse(input: syn::parse::ParseStream) -> Result { 953 | let ident = input.parse()?; 954 | let assign_token = input.parse()?; 955 | let expr = input.parse()?; 956 | let semi_token = input.parse()?; 957 | 958 | Ok(Self { 959 | ident, 960 | assign_token, 961 | expr, 962 | _semi_token: semi_token, 963 | }) 964 | } 965 | } 966 | 967 | /// A visitor that mutates expressions / literals so that we can wrap them in Expr. If the expression is already in 968 | /// shades::expr::Expr, the expression is left unchanged. 969 | struct ExprVisitor; 970 | 971 | impl VisitMut for ExprVisitor { 972 | fn visit_type_mut(&mut self, ty: &mut Type) { 973 | *ty = parse_quote! { shades::expr::Expr<#ty> }; 974 | } 975 | 976 | fn visit_expr_mut(&mut self, i: &mut Expr) { 977 | match i { 978 | Expr::Array(a) => { 979 | for expr in &mut a.elems { 980 | self.visit_expr_mut(expr); 981 | } 982 | 983 | *i = parse_quote! { shades::expr::Expr::from(#a) }; 984 | } 985 | 986 | Expr::Assign(a) => { 987 | self.visit_expr_mut(&mut a.right); 988 | } 989 | 990 | Expr::AssignOp(a) => { 991 | self.visit_expr_mut(&mut a.right); 992 | } 993 | 994 | Expr::Binary(b) => { 995 | self.visit_expr_mut(&mut b.left); 996 | self.visit_expr_mut(&mut b.right); 997 | } 998 | 999 | Expr::Block(b) => { 1000 | for stmt in &mut b.block.stmts { 1001 | match stmt { 1002 | syn::Stmt::Expr(expr) | syn::Stmt::Semi(expr, _) => { 1003 | self.visit_expr_mut(expr); 1004 | } 1005 | 1006 | _ => (), 1007 | } 1008 | } 1009 | } 1010 | 1011 | Expr::Box(e) => { 1012 | self.visit_expr_mut(&mut e.expr); 1013 | } 1014 | 1015 | Expr::Call(c) => { 1016 | let ident = &c.func; 1017 | 1018 | for arg in &mut c.args { 1019 | self.visit_expr_mut(arg); 1020 | } 1021 | 1022 | let args = c.args.iter(); 1023 | let call = quote! { 1024 | #ident.call(#(#args),*) 1025 | }; 1026 | 1027 | *i = parse_quote!(#call); 1028 | } 1029 | 1030 | Expr::Cast(c) => { 1031 | self.visit_expr_mut(&mut c.expr); 1032 | } 1033 | 1034 | Expr::Lit(_) => { 1035 | *i = parse_quote! { shades::expr::Expr::from(#i) }; 1036 | } 1037 | 1038 | Expr::MethodCall(c) => { 1039 | for arg in &mut c.args { 1040 | self.visit_expr_mut(arg); 1041 | } 1042 | } 1043 | 1044 | Expr::Paren(p) => { 1045 | self.visit_expr_mut(&mut p.expr); 1046 | } 1047 | 1048 | Expr::Return(r) => { 1049 | if let Some(ref mut expr) = r.expr { 1050 | self.visit_expr_mut(expr); 1051 | } 1052 | } 1053 | 1054 | Expr::Unary(u) => { 1055 | self.visit_expr_mut(&mut u.expr); 1056 | } 1057 | 1058 | Expr::Path(p) => { 1059 | let e = parse_quote! { #p.clone() }; 1060 | *i = e; 1061 | } 1062 | 1063 | Expr::Index(idx) => { 1064 | let expr = &idx.expr; 1065 | let index = &idx.index; 1066 | 1067 | *i = parse_quote! { shades::expr::Expr::from(#expr.clone()).at(shades::expr::Expr::from(#index.clone()))} 1068 | } 1069 | 1070 | _ => (), 1071 | } 1072 | } 1073 | } 1074 | -------------------------------------------------------------------------------- /shades/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.6 2 | 3 | > Jul 12, 2021 4 | 5 | - Allow to call `Neg` and `Not` on `Var`. 6 | - Add `HasX`, `HasY`, `HasZ` and `HasW` to make it easier to get `VN<_>` values. 7 | - Add `Bounded` to the public interface. 8 | - Fix `lit!` for `V2`. 9 | - Add support for array literals. 10 | 11 | # 0.3.5 12 | 13 | > Feb 21, 2021 14 | 15 | - Change the way `writer::glsl` writers work internally: they now use `std::fmt::Write` instead of using directly 16 | `String`. 17 | - Add the `shades::writer::glsl::write_shader` that uses `std::fmt::Write` directly. 18 | 19 | # 0.3.4 20 | 21 | > Feb 20, 2021 22 | 23 | - Fix matrices types in the GLSL writer. 24 | 25 | # 0.3.3 26 | 27 | > Feb 20, 2021 28 | 29 | - Fix matrices binary operators. 30 | 31 | # 0.3.2 32 | 33 | > Feb 20, 2021 34 | 35 | - Add matrices. 36 | 37 | # 0.3.1 38 | 39 | > Feb 19, 2021 40 | 41 | - Add the `uniforms!` macro to safely create uniforms. 42 | 43 | # 0.3 44 | 45 | > Feb 19, 2021 46 | 47 | - Add matrices and make `PrimType` a non-exhaustive `enum` so that we can add more types later without breaking the API. 48 | 49 | # 0.2 50 | 51 | > Feb 15, 2021 52 | 53 | - Changed the API to make it easier to use. 54 | - Documented as much things as possible. It’s likely some things are still missing. They will be added, if any, in minor 55 | bumps of the library. 56 | 57 | # 0.1 58 | 59 | > Jan 18, 2021 60 | 61 | - Initial revision. 62 | -------------------------------------------------------------------------------- /shades/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shades" 3 | version = "0.4.0" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "An EDSL for shading languages" 7 | keywords = ["edsl", "shader", "glsl", "spir-v"] 8 | categories = ["graphics"] 9 | homepage = "https://github.com/phaazon/shades" 10 | repository = "https://github.com/phaazon/shades" 11 | documentation = "https://docs.rs/shades" 12 | readme = "../README.md" 13 | edition = "2018" 14 | 15 | [badges] 16 | maintenance = { status = "actively-developed" } 17 | 18 | [features] 19 | default = ["edsl"] 20 | edsl = ["shades-edsl"] 21 | 22 | [dependencies] 23 | shades-edsl = { version = "0.1", path = "../shades-edsl", optional = true } 24 | -------------------------------------------------------------------------------- /shades/src/builtin.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 2 | pub enum BuiltIn { 3 | Vertex(VertexBuiltIn), 4 | TessCtrl(TessCtrlBuiltIn), 5 | TessEval(TessEvalBuiltIn), 6 | Geometry(GeometryBuiltIn), 7 | Fragment(FragmentBuiltIn), 8 | } 9 | 10 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 11 | pub enum VertexBuiltIn { 12 | VertexID, 13 | InstanceID, 14 | BaseVertex, 15 | BaseInstance, 16 | Position, 17 | PointSize, 18 | ClipDistance, 19 | } 20 | 21 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 22 | pub enum TessCtrlBuiltIn { 23 | MaxPatchVerticesIn, 24 | PatchVerticesIn, 25 | PrimitiveID, 26 | InvocationID, 27 | TessellationLevelOuter, 28 | TessellationLevelInner, 29 | In, 30 | Out, 31 | Position, 32 | PointSize, 33 | ClipDistance, 34 | CullDistance, 35 | } 36 | 37 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 38 | pub enum TessEvalBuiltIn { 39 | TessCoord, 40 | MaxPatchVerticesIn, 41 | PatchVerticesIn, 42 | PrimitiveID, 43 | TessellationLevelOuter, 44 | TessellationLevelInner, 45 | In, 46 | Out, 47 | Position, 48 | PointSize, 49 | ClipDistance, 50 | CullDistance, 51 | } 52 | 53 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 54 | pub enum GeometryBuiltIn { 55 | In, 56 | Out, 57 | Position, 58 | PointSize, 59 | ClipDistance, 60 | CullDistance, 61 | PrimitiveID, 62 | PrimitiveIDIn, 63 | InvocationID, 64 | Layer, 65 | ViewportIndex, 66 | } 67 | 68 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 69 | pub enum FragmentBuiltIn { 70 | FragCoord, 71 | FrontFacing, 72 | PointCoord, 73 | SampleID, 74 | SamplePosition, 75 | SampleMaskIn, 76 | ClipDistance, 77 | CullDistance, 78 | PrimitiveID, 79 | Layer, 80 | ViewportIndex, 81 | FragDepth, 82 | SampleMask, 83 | HelperInvocation, 84 | } 85 | -------------------------------------------------------------------------------- /shades/src/env.rs: -------------------------------------------------------------------------------- 1 | //! Shader environments. 2 | //! 3 | //! An environment is a set of expressions available to a shader stage to manipulate values coming from the outside of 4 | //! the stage. It’s akin to system environment (i.e. [`std::env::var`]), but for shaders. 5 | //! 6 | //! Environment variables are typically set by the backend technology, such as OpenGL or Vulkan. 7 | 8 | use crate::types::Type; 9 | 10 | /// Environment. 11 | /// 12 | /// Environment types must provide [`Environment::Env`], which is the type that will be available on the EDSL side. The 13 | /// reason for that is to authorize creating proc-macro to automatically derive [`Environment`] for their types without 14 | /// having to use [`Expr`] types. 15 | /// 16 | /// You can use [`()`] if you do not want to use any environment. 17 | pub trait Environment { 18 | /// Environment type available on the EDSL side. 19 | type Env; 20 | 21 | /// Get the environment. 22 | fn env() -> Self::Env; 23 | 24 | /// Environment set. 25 | /// 26 | /// This is the list of environment variables available in [`Environment::Env`]. The mapping is a [`String`] to [`Type`]. 27 | /// You can easily get the [`Type`] by using [`ToType::ty`]. 28 | fn env_set() -> Vec<(String, Type)>; 29 | } 30 | 31 | impl Environment for () { 32 | type Env = (); 33 | 34 | fn env() -> Self::Env { 35 | () 36 | } 37 | 38 | fn env_set() -> Vec<(String, Type)> { 39 | Vec::new() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /shades/src/erased.rs: -------------------------------------------------------------------------------- 1 | //! Types that support type erasure. 2 | //! 3 | //! Type erasure is important when typing provides the safe abstraction over raw data, and that you need to manipulate 4 | //! the raw data without the safe typing abstraction. It’s typical once you have passed the typing interface 5 | //! (user-side) and that you know that you won’t violate invariants. 6 | 7 | /// Associated erased type. 8 | pub trait Erased { 9 | /// Erased version. 10 | type Erased; 11 | 12 | /// Consumer the typed interface and return the erased version. 13 | fn to_erased(self) -> Self::Erased; 14 | 15 | /// Immutable access to the erased version. 16 | fn erased(&self) -> &Self::Erased; 17 | 18 | /// Mutable access to the erased version. 19 | fn erased_mut(&mut self) -> &mut Self::Erased; 20 | } 21 | -------------------------------------------------------------------------------- /shades/src/expr.rs: -------------------------------------------------------------------------------- 1 | //! EDSL expressions. 2 | //! 3 | //! Expressions are read-only items representing different kind of objects, such as literal values, operations between 4 | //! expressions (unary, binary, function calls, etc.), arrays of expressions, etc. etc. 5 | //! 6 | //! There is an important relationship between expressions ([`Expr`]) and variables [`Var`]. [`Var`] are basically 7 | //! read/write expressions and can coerce to expressions; the other way around is not possible. 8 | use crate::{ 9 | builtin::BuiltIn, 10 | erased::Erased, 11 | fun::ErasedFunHandle, 12 | scope::ScopedHandle, 13 | swizzle::Swizzle, 14 | types::{ToType, Type, M22, M33, M44, V2, V3, V4}, 15 | var::Var, 16 | }; 17 | use std::{marker::PhantomData, ops}; 18 | 19 | /// Representation of an expression. 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub enum ErasedExpr { 22 | // scalars 23 | LitInt(i32), 24 | LitUInt(u32), 25 | LitFloat(f32), 26 | LitBool(bool), 27 | // vectors 28 | LitInt2([i32; 2]), 29 | LitUInt2([u32; 2]), 30 | LitFloat2([f32; 2]), 31 | LitBool2([bool; 2]), 32 | LitInt3([i32; 3]), 33 | LitUInt3([u32; 3]), 34 | LitFloat3([f32; 3]), 35 | LitBool3([bool; 3]), 36 | LitInt4([i32; 4]), 37 | LitUInt4([u32; 4]), 38 | LitFloat4([f32; 4]), 39 | LitBool4([bool; 4]), 40 | // matrices 41 | LitM22(M22), 42 | // LitM23(M23), 43 | // LitM24(M24), 44 | // LitM32(M32), 45 | LitM33(M33), 46 | // LitM34(M34), 47 | // LitM42(M42), 48 | // LitM43(M43), 49 | LitM44(M44), 50 | // arrays 51 | Array(Type, Vec), 52 | // var 53 | Var(ScopedHandle), 54 | // built-in functions and operators 55 | Not(Box), 56 | And(Box, Box), 57 | Or(Box, Box), 58 | Xor(Box, Box), 59 | BitOr(Box, Box), 60 | BitAnd(Box, Box), 61 | BitXor(Box, Box), 62 | Neg(Box), 63 | Add(Box, Box), 64 | Sub(Box, Box), 65 | Mul(Box, Box), 66 | Div(Box, Box), 67 | Rem(Box, Box), 68 | Shl(Box, Box), 69 | Shr(Box, Box), 70 | Eq(Box, Box), 71 | Neq(Box, Box), 72 | Lt(Box, Box), 73 | Lte(Box, Box), 74 | Gt(Box, Box), 75 | Gte(Box, Box), 76 | // function call 77 | FunCall(ErasedFunHandle, Vec), 78 | // swizzle 79 | Swizzle(Box, Swizzle), 80 | // field expression, as in a struct Foo { float x; }, foo.x is an Expr representing the x field on object foo 81 | Field { object: Box, field: Box }, 82 | ArrayLookup { object: Box, index: Box }, 83 | } 84 | 85 | impl ErasedExpr { 86 | pub(crate) const fn new_builtin(builtin: BuiltIn) -> Self { 87 | ErasedExpr::Var(ScopedHandle::builtin(builtin)) 88 | } 89 | } 90 | 91 | /// Expression representation. 92 | /// 93 | /// An expression is anything that carries a (typed) value and that can be combined in various ways with other 94 | /// expressions. A literal, a constant or a variable are all expressions. The sum (as in `a + b`) of two expressions is 95 | /// also an expression. A function call returning an expression is also an expression, as in `a * sin(b)`. Accessing an 96 | /// element in an array (which is an expression as it carries items) via an index (an expression) is also an 97 | /// expression — e.g. `levels[y * HEIGHT + x] * size`. The same thing applies to field access, swizzling, etc. etc. 98 | /// 99 | /// On a general note, expressions are pretty central to the EDSL, as they are the _lower level concept_ you will be 100 | /// able to manipulate. Expressions are side effect free, so a variable, for instance, can either be considered as an 101 | /// expression or not. If `x` is a variable (see [`Var`]), then `x * 10` is an expression, but using `x` to mutate its 102 | /// content does not make a use of `x` as an expression. It means that expressions are read-only and even though you 103 | /// can go from higher constructs (like variables) to expressions, the opposite direction is forbidden. 104 | /// 105 | /// # Literals 106 | /// 107 | /// The most obvious kind of expression is a literal — e.g. `1`, `false`, `3.14`, etc. Any type `T` that defines an 108 | /// implementor `From for Expr` can be used as literal. You can then use, for instance, `1.into()`, 109 | /// `Expr::from(1)`, etc. 110 | /// 111 | /// > That is a concern of yours only if you do not use [shades-edsl]. 112 | /// 113 | /// # Expressions from side-effects 114 | /// 115 | /// Some side-effects will create expressions, such as creating a variable or a constant. Most of the time, you 116 | /// shouldn’t have to worry about the type of the expression as it should be inferred based on the side-effect. 117 | /// 118 | /// # Expression macros 119 | /// 120 | /// Some macros will create expressions for you, such as [`vec2!`](vec2), [`vec3!`](vec3) and [`vec4!`](vec4) or the 121 | /// [`sw!`](sw) macros. 122 | /// 123 | /// [shades-edsl]: https://crates.io/crates/shades-edsl 124 | #[derive(Debug)] 125 | pub struct Expr 126 | where 127 | T: ?Sized, 128 | { 129 | pub(crate) erased: ErasedExpr, 130 | _phantom: PhantomData, 131 | } 132 | 133 | impl Clone for Expr 134 | where 135 | T: ?Sized, 136 | { 137 | fn clone(&self) -> Self { 138 | Self::new(self.erased.clone()) 139 | } 140 | } 141 | 142 | impl Erased for Expr { 143 | type Erased = ErasedExpr; 144 | 145 | fn to_erased(self) -> Self::Erased { 146 | self.erased 147 | } 148 | 149 | fn erased(&self) -> &Self::Erased { 150 | &self.erased 151 | } 152 | 153 | fn erased_mut(&mut self) -> &mut Self::Erased { 154 | &mut self.erased 155 | } 156 | } 157 | 158 | impl Expr 159 | where 160 | T: ?Sized, 161 | { 162 | /// Type an [`ErasedExpr`] and return it wrapped in [`Expr`]. 163 | pub const fn new(erased: ErasedExpr) -> Self { 164 | Self { 165 | erased, 166 | _phantom: PhantomData, 167 | } 168 | } 169 | 170 | /// Create a new input. 171 | /// 172 | /// You should use this function when implementing [`Inputs::input()`]. 173 | pub const fn new_input(handle: u16) -> Self { 174 | Self::new(ErasedExpr::Var(ScopedHandle::Input(handle))) 175 | } 176 | 177 | /// Create a new output. 178 | /// 179 | /// You should use this function when implementing [`Outputs::output()`]. 180 | pub fn new_output(handle: u16) -> Self { 181 | Self::new(ErasedExpr::Var(ScopedHandle::Output(handle))) 182 | } 183 | 184 | /// Create a new environment variable. 185 | /// 186 | /// You should use this function when implementing [`Environment::env()`]. 187 | pub fn new_env(name: impl Into) -> Self { 188 | Self::new(ErasedExpr::Var(ScopedHandle::Env(name.into()))) 189 | } 190 | 191 | /// Create a new function argument. 192 | /// 193 | /// You shouldn’t have to use that function, as its main purpose is to be used by [shades-edsl]. 194 | /// 195 | /// [shades-edsl]: https://crates.io/crates/shades-edsl 196 | pub const fn new_fun_arg(handle: u16) -> Self { 197 | Self::new(ErasedExpr::Var(ScopedHandle::FunArg(handle))) 198 | } 199 | 200 | /// Equality expression. 201 | /// 202 | /// This method builds an expression representing the equality between two expressions. 203 | /// 204 | /// # Return 205 | /// 206 | /// An [`Expr`] representing the equality between the two input expressions. 207 | pub fn eq(&self, rhs: impl Into>) -> Expr { 208 | Expr::new(ErasedExpr::Eq( 209 | Box::new(self.erased.clone()), 210 | Box::new(rhs.into().erased), 211 | )) 212 | } 213 | 214 | /// Inequality expression. 215 | /// 216 | /// This method builds an expression representing the inequality between two expressions. 217 | /// 218 | /// # Return 219 | /// 220 | /// An [`Expr`] representing the inequality between the two input expressions. 221 | pub fn neq(&self, rhs: impl Into>) -> Expr { 222 | Expr::new(ErasedExpr::Neq( 223 | Box::new(self.erased.clone()), 224 | Box::new(rhs.into().erased), 225 | )) 226 | } 227 | } 228 | 229 | impl Expr 230 | where 231 | T: PartialOrd, 232 | { 233 | /// Less-than expression. 234 | /// 235 | /// This method builds an expression representing the binary operation `a < b`. 236 | /// 237 | /// # Return 238 | /// 239 | /// An [`Expr`] representing `a < b`. 240 | pub fn lt(&self, rhs: impl Into>) -> Expr { 241 | Expr::new(ErasedExpr::Lt( 242 | Box::new(self.erased.clone()), 243 | Box::new(rhs.into().erased), 244 | )) 245 | } 246 | 247 | /// Less-than-or-equal expression. 248 | /// 249 | /// This method builds an expression representing the binary operation `a <= b`. 250 | /// 251 | /// # Return 252 | /// 253 | /// An [`Expr`] representing `a <= b`. 254 | pub fn lte(&self, rhs: impl Into>) -> Expr { 255 | Expr::new(ErasedExpr::Lte( 256 | Box::new(self.erased.clone()), 257 | Box::new(rhs.into().erased), 258 | )) 259 | } 260 | 261 | /// Greater-than expression. 262 | /// 263 | /// This method builds an expression representing the binary operation `a > b`. 264 | /// 265 | /// # Return 266 | /// 267 | /// An [`Expr`] representing `a > b`. 268 | pub fn gt(&self, rhs: impl Into>) -> Expr { 269 | Expr::new(ErasedExpr::Gt( 270 | Box::new(self.erased.clone()), 271 | Box::new(rhs.into().erased), 272 | )) 273 | } 274 | 275 | /// Less-than-or-equal expression. 276 | /// 277 | /// This method builds an expression representing the binary operation `a <= b`. 278 | /// 279 | /// # Return 280 | /// 281 | /// An [`Expr`] representing `a <= b`. 282 | pub fn gte(&self, rhs: impl Into>) -> Expr { 283 | Expr::new(ErasedExpr::Gte( 284 | Box::new(self.erased.clone()), 285 | Box::new(rhs.into().erased), 286 | )) 287 | } 288 | } 289 | 290 | impl Expr { 291 | /// Logical _and_ expression. 292 | /// 293 | /// This method builds an expression representing the logical operation `a AND b`. 294 | /// 295 | /// # Return 296 | /// 297 | /// An [`Expr`] representing `a AND b`. 298 | pub fn and(&self, rhs: impl Into>) -> Expr { 299 | Expr::new(ErasedExpr::And( 300 | Box::new(self.erased.clone()), 301 | Box::new(rhs.into().erased), 302 | )) 303 | } 304 | 305 | /// Logical _or_ expression. 306 | /// 307 | /// This method builds an expression representing the logical operation `a OR b`. 308 | /// 309 | /// # Return 310 | /// 311 | /// An [`Expr`] representing `a OR b`. 312 | pub fn or(&self, rhs: impl Into>) -> Expr { 313 | Expr::new(ErasedExpr::Or( 314 | Box::new(self.erased.clone()), 315 | Box::new(rhs.into().erased), 316 | )) 317 | } 318 | 319 | /// Logical _exclusive or_ expression. 320 | /// 321 | /// This method builds an expression representing the logical operation `a XOR b`. 322 | /// 323 | /// # Return 324 | /// 325 | /// An [`Expr`] representing `a XOR b`. 326 | pub fn xor(&self, rhs: impl Into>) -> Expr { 327 | Expr::new(ErasedExpr::Xor( 328 | Box::new(self.erased.clone()), 329 | Box::new(rhs.into().erased), 330 | )) 331 | } 332 | } 333 | 334 | impl Expr<[T]> { 335 | /// Array lookup. 336 | /// 337 | /// The expression `a.at(i)` represents an _array lookup_, where `a` is an array — which type must be either 338 | /// [`Expr<[T]>`](Expr) or [`Expr<[T; N]>`](Expr) – and `i` is an [`Expr`]. 339 | /// 340 | /// # Return 341 | /// 342 | /// The resulting [`Expr`] represents the array lookup in `a` at index `i`. 343 | pub fn at(&self, index: Expr) -> Expr { 344 | Expr::new(ErasedExpr::ArrayLookup { 345 | object: Box::new(self.erased.clone()), 346 | index: Box::new(index.erased), 347 | }) 348 | } 349 | } 350 | 351 | impl Expr<[T; N]> { 352 | /// Array lookup. 353 | /// 354 | /// The expression `a.at(i)` represents an _array lookup_, where `a` is an array — which type must be either 355 | /// [`Expr<[T]>`](Expr) or [`Expr<[T; N]>`](Expr) – and `i` is an [`Expr`]. 356 | /// 357 | /// # Return 358 | /// 359 | /// The resulting [`Expr`] represents the array lookup in `a` at index `i`. 360 | pub fn at(&self, index: impl Into>) -> Expr { 361 | Expr::new(ErasedExpr::ArrayLookup { 362 | object: Box::new(self.erased.clone()), 363 | index: Box::new(index.into().erased), 364 | }) 365 | } 366 | } 367 | 368 | // not 369 | macro_rules! impl_Not_Expr { 370 | ($t:ty) => { 371 | impl ops::Not for Expr<$t> { 372 | type Output = Self; 373 | 374 | fn not(self) -> Self::Output { 375 | Expr::new(ErasedExpr::Not(Box::new(self.erased))) 376 | } 377 | } 378 | 379 | impl ops::Not for Var<$t> { 380 | type Output = Expr<$t>; 381 | 382 | fn not(self) -> Self::Output { 383 | Expr::new(ErasedExpr::Not(Box::new(self.0.erased))) 384 | } 385 | } 386 | }; 387 | } 388 | 389 | impl_Not_Expr!(bool); 390 | impl_Not_Expr!(V2); 391 | impl_Not_Expr!(V3); 392 | impl_Not_Expr!(V4); 393 | 394 | // neg 395 | macro_rules! impl_Neg { 396 | ($t:ty) => { 397 | impl ops::Neg for Expr<$t> { 398 | type Output = Self; 399 | 400 | fn neg(self) -> Self::Output { 401 | Expr::new(ErasedExpr::Neg(Box::new(self.erased))) 402 | } 403 | } 404 | 405 | impl ops::Neg for Var<$t> { 406 | type Output = Expr<$t>; 407 | 408 | fn neg(self) -> Self::Output { 409 | Expr::new(ErasedExpr::Neg(Box::new(self.0.erased))) 410 | } 411 | } 412 | }; 413 | } 414 | 415 | impl_Neg!(i32); 416 | impl_Neg!(V2); 417 | impl_Neg!(V3); 418 | impl_Neg!(V4); 419 | 420 | impl_Neg!(u32); 421 | impl_Neg!(V2); 422 | impl_Neg!(V3); 423 | impl_Neg!(V4); 424 | 425 | impl_Neg!(f32); 426 | impl_Neg!(V2); 427 | impl_Neg!(V3); 428 | impl_Neg!(V4); 429 | 430 | // binary arithmetic and logical (+, -, *, /, %) 431 | // binop 432 | macro_rules! impl_binop_Expr { 433 | ($op:ident, $meth_name:ident, $a:ty, $b:ty) => { 434 | impl_binop_Expr!($op, $meth_name, $a, $b, $a); 435 | }; 436 | 437 | ($op:ident, $meth_name:ident, $a:ty, $b:ty, $r:ty) => { 438 | // expr OP expr 439 | impl<'a> ops::$op> for Expr<$a> { 440 | type Output = Expr<$r>; 441 | 442 | fn $meth_name(self, rhs: Expr<$b>) -> Self::Output { 443 | Expr::new(ErasedExpr::$op(Box::new(self.erased), Box::new(rhs.erased))) 444 | } 445 | } 446 | }; 447 | } 448 | 449 | // or 450 | impl_binop_Expr!(BitOr, bitor, bool, bool); 451 | impl_binop_Expr!(BitOr, bitor, V2, V2); 452 | impl_binop_Expr!(BitOr, bitor, V2, bool); 453 | impl_binop_Expr!(BitOr, bitor, V3, V3); 454 | impl_binop_Expr!(BitOr, bitor, V3, bool); 455 | impl_binop_Expr!(BitOr, bitor, V4, V4); 456 | impl_binop_Expr!(BitOr, bitor, V4, bool); 457 | 458 | // and 459 | impl_binop_Expr!(BitAnd, bitand, bool, bool); 460 | impl_binop_Expr!(BitAnd, bitand, V2, V2); 461 | impl_binop_Expr!(BitAnd, bitand, V2, bool); 462 | impl_binop_Expr!(BitAnd, bitand, V3, V3); 463 | impl_binop_Expr!(BitAnd, bitand, V3, bool); 464 | impl_binop_Expr!(BitAnd, bitand, V4, V4); 465 | impl_binop_Expr!(BitAnd, bitand, V4, bool); 466 | 467 | // xor 468 | impl_binop_Expr!(BitXor, bitxor, bool, bool); 469 | impl_binop_Expr!(BitXor, bitxor, V2, V2); 470 | impl_binop_Expr!(BitXor, bitxor, V2, bool); 471 | impl_binop_Expr!(BitXor, bitxor, V3, V3); 472 | impl_binop_Expr!(BitXor, bitxor, V3, bool); 473 | impl_binop_Expr!(BitXor, bitxor, V4, V4); 474 | impl_binop_Expr!(BitXor, bitxor, V4, bool); 475 | 476 | /// Run a macro on all supported types to generate the impl for them 477 | /// 478 | /// The macro has to have to take two `ty` as argument and yield a `std::ops` trait implementor. 479 | macro_rules! impl_binarith_Expr { 480 | ($op:ident, $meth_name:ident) => { 481 | impl_binop_Expr!($op, $meth_name, i32, i32); 482 | impl_binop_Expr!($op, $meth_name, V2, V2); 483 | impl_binop_Expr!($op, $meth_name, V2, i32); 484 | impl_binop_Expr!($op, $meth_name, V3, V3); 485 | impl_binop_Expr!($op, $meth_name, V3, i32); 486 | impl_binop_Expr!($op, $meth_name, V4, V4); 487 | impl_binop_Expr!($op, $meth_name, V4, i32); 488 | 489 | impl_binop_Expr!($op, $meth_name, u32, u32); 490 | impl_binop_Expr!($op, $meth_name, V2, V2); 491 | impl_binop_Expr!($op, $meth_name, V2, u32); 492 | impl_binop_Expr!($op, $meth_name, V3, V3); 493 | impl_binop_Expr!($op, $meth_name, V3, u32); 494 | impl_binop_Expr!($op, $meth_name, V4, V4); 495 | impl_binop_Expr!($op, $meth_name, V4, u32); 496 | 497 | impl_binop_Expr!($op, $meth_name, f32, f32); 498 | impl_binop_Expr!($op, $meth_name, V2, V2); 499 | impl_binop_Expr!($op, $meth_name, V2, f32); 500 | impl_binop_Expr!($op, $meth_name, V3, V3); 501 | impl_binop_Expr!($op, $meth_name, V3, f32); 502 | impl_binop_Expr!($op, $meth_name, V4, V4); 503 | impl_binop_Expr!($op, $meth_name, V4, f32); 504 | }; 505 | } 506 | 507 | impl_binarith_Expr!(Add, add); 508 | impl_binarith_Expr!(Sub, sub); 509 | impl_binarith_Expr!(Mul, mul); 510 | impl_binarith_Expr!(Div, div); 511 | 512 | impl_binop_Expr!(Rem, rem, f32, f32); 513 | impl_binop_Expr!(Rem, rem, V2, V2); 514 | impl_binop_Expr!(Rem, rem, V2, f32); 515 | impl_binop_Expr!(Rem, rem, V3, V3); 516 | impl_binop_Expr!(Rem, rem, V3, f32); 517 | impl_binop_Expr!(Rem, rem, V4, V4); 518 | impl_binop_Expr!(Rem, rem, V4, f32); 519 | 520 | impl_binop_Expr!(Mul, mul, M22, M22); 521 | impl_binop_Expr!(Mul, mul, M22, V2, V2); 522 | impl_binop_Expr!(Mul, mul, V2, M22, M22); 523 | impl_binop_Expr!(Mul, mul, M33, M33); 524 | impl_binop_Expr!(Mul, mul, M33, V3, V3); 525 | impl_binop_Expr!(Mul, mul, V3, M33, M33); 526 | impl_binop_Expr!(Mul, mul, M44, M44); 527 | impl_binop_Expr!(Mul, mul, M44, V4, V4); 528 | impl_binop_Expr!(Mul, mul, V4, M44, M44); 529 | 530 | macro_rules! impl_binshift_Expr { 531 | ($op:ident, $meth_name:ident, $ty:ty) => { 532 | // expr OP expr 533 | impl ops::$op> for Expr<$ty> { 534 | type Output = Expr<$ty>; 535 | 536 | fn $meth_name(self, rhs: Expr) -> Self::Output { 537 | Expr::new(ErasedExpr::$op(Box::new(self.erased), Box::new(rhs.erased))) 538 | } 539 | } 540 | }; 541 | } 542 | 543 | /// Binary shift generating macro. 544 | macro_rules! impl_binshifts_Expr { 545 | ($op:ident, $meth_name:ident) => { 546 | impl_binshift_Expr!($op, $meth_name, i32); 547 | impl_binshift_Expr!($op, $meth_name, V2); 548 | impl_binshift_Expr!($op, $meth_name, V3); 549 | impl_binshift_Expr!($op, $meth_name, V4); 550 | 551 | impl_binshift_Expr!($op, $meth_name, u32); 552 | impl_binshift_Expr!($op, $meth_name, V2); 553 | impl_binshift_Expr!($op, $meth_name, V3); 554 | impl_binshift_Expr!($op, $meth_name, V4); 555 | 556 | impl_binshift_Expr!($op, $meth_name, f32); 557 | impl_binshift_Expr!($op, $meth_name, V2); 558 | impl_binshift_Expr!($op, $meth_name, V3); 559 | impl_binshift_Expr!($op, $meth_name, V4); 560 | }; 561 | } 562 | 563 | impl_binshifts_Expr!(Shl, shl); 564 | impl_binshifts_Expr!(Shr, shr); 565 | 566 | macro_rules! impl_From_Expr_scalar { 567 | ($t:ty, $q:ident) => { 568 | impl From<$t> for Expr<$t> { 569 | fn from(a: $t) -> Self { 570 | Self::new(ErasedExpr::$q(a)) 571 | } 572 | } 573 | }; 574 | } 575 | 576 | impl_From_Expr_scalar!(i32, LitInt); 577 | impl_From_Expr_scalar!(u32, LitUInt); 578 | impl_From_Expr_scalar!(f32, LitFloat); 579 | impl_From_Expr_scalar!(bool, LitBool); 580 | 581 | macro_rules! impl_From_Expr_vn { 582 | ($t:ty, $q:ident) => { 583 | impl From<$t> for Expr<$t> { 584 | fn from(a: $t) -> Self { 585 | Self::new(ErasedExpr::$q(a.0)) 586 | } 587 | } 588 | }; 589 | } 590 | 591 | impl_From_Expr_vn!(V2, LitInt2); 592 | impl_From_Expr_vn!(V2, LitUInt2); 593 | impl_From_Expr_vn!(V2, LitFloat2); 594 | impl_From_Expr_vn!(V2, LitBool2); 595 | impl_From_Expr_vn!(V3, LitInt3); 596 | impl_From_Expr_vn!(V3, LitUInt3); 597 | impl_From_Expr_vn!(V3, LitFloat3); 598 | impl_From_Expr_vn!(V3, LitBool3); 599 | impl_From_Expr_vn!(V4, LitInt4); 600 | impl_From_Expr_vn!(V4, LitUInt4); 601 | impl_From_Expr_vn!(V4, LitFloat4); 602 | impl_From_Expr_vn!(V4, LitBool4); 603 | 604 | impl From<[T; N]> for Expr<[T; N]> 605 | where 606 | Expr: From, 607 | T: Clone + ToType, 608 | { 609 | fn from(array: [T; N]) -> Self { 610 | let array = array 611 | .iter() 612 | .cloned() 613 | .map(|t| Expr::from(t).erased) 614 | .collect(); 615 | Self::new(ErasedExpr::Array(<[T; N] as ToType>::ty(), array)) 616 | } 617 | } 618 | 619 | impl<'a, T, const N: usize> From<&'a [T; N]> for Expr<[T; N]> 620 | where 621 | Expr: From, 622 | T: Clone + ToType, 623 | { 624 | fn from(array: &'a [T; N]) -> Self { 625 | let array = array 626 | .iter() 627 | .cloned() 628 | .map(|t| Expr::from(t).erased) 629 | .collect(); 630 | Self::new(ErasedExpr::Array(<[T; N] as ToType>::ty(), array)) 631 | } 632 | } 633 | 634 | impl From<[Expr; N]> for Expr<[T; N]> 635 | where 636 | Expr: From, 637 | T: ToType, 638 | { 639 | fn from(array: [Expr; N]) -> Self { 640 | let array = array.iter().cloned().map(|e| e.erased).collect(); 641 | Self::new(ErasedExpr::Array(<[T; N] as ToType>::ty(), array)) 642 | } 643 | } 644 | 645 | impl<'a, T, const N: usize> From<&'a [Expr; N]> for Expr<[T; N]> 646 | where 647 | Expr: From, 648 | T: ToType, 649 | { 650 | fn from(array: &'a [Expr; N]) -> Self { 651 | let array = array.iter().cloned().map(|e| e.erased).collect(); 652 | Self::new(ErasedExpr::Array(<[T; N] as ToType>::ty(), array)) 653 | } 654 | } 655 | 656 | /// Create 2D scalar vectors via different forms. 657 | /// 658 | /// This macro allows to create 2D ([`V2`]) scalar vectors from two forms: 659 | /// 660 | /// - `vec2!(xy)`, which acts as the cast operator. Only types `T` satisfying [`Vec2`] are castable. 661 | /// - `vec2!(x, y)`, which builds a [`V2`] for `x: T` and `y: T`. 662 | #[macro_export] 663 | macro_rules! vec2 { 664 | ($a:expr) => { 665 | todo!("vec2 cast operator missing"); 666 | }; 667 | 668 | ($xy:expr, $z:expr) => {{ 669 | use $crate::types::Vec2 as _; 670 | $crate::expr::Expr::vec2(($crate::expr::Expr::from($xy), $crate::expr::Expr::from($z))) 671 | }}; 672 | } 673 | 674 | /// Create 3D scalar vectors via different forms. 675 | /// 676 | /// This macro allows to create 3D ([`V3`]) scalar vectors from several forms: 677 | /// 678 | /// - `vec3!(xyz)`, which acts as the cast operator. Only types `T` satisfying [`Vec3`] are castable. 679 | /// - `vec3!(xy, z)`, which builds a [`V3`] with `xy` a value that can be turned into a `Expr>` and `z: T` 680 | /// - `vec3!(x, y, z)`, which builds a [`V3`] for `x: T`, `y: T` and `z: T`. 681 | #[macro_export] 682 | macro_rules! vec3 { 683 | ($a:expr) => { 684 | todo!("vec3 cast operator missing"); 685 | }; 686 | 687 | ($xy:expr, $z:expr) => {{ 688 | use $crate::types::Vec3 as _; 689 | $crate::expr::Expr::vec3(($crate::expr::Expr::from($xy), $crate::expr::Expr::from($z))) 690 | }}; 691 | 692 | ($x:expr, $y:expr, $z:expr) => {{ 693 | use $crate::types::Vec3 as _; 694 | $crate::expr::Expr::vec3(( 695 | $crate::expr::Expr::from($x), 696 | $crate::expr::Expr::from($y), 697 | $crate::expr::Expr::from($z), 698 | )) 699 | }}; 700 | } 701 | 702 | /// Create 4D scalar vectors via different forms. 703 | /// 704 | /// This macro allows to create 4D ([`V4`]) scalar vectors from several forms: 705 | /// 706 | /// - `vec4!(xyzw)`, which acts as the cast operator. Only types `T` satisfying [`Vec4`] are castable. 707 | /// - `vec4!(xyz, w)`, which builds a [`V4`] with `xyz` a value that can be turned into a `Expr>` and `w: T`. 708 | /// - `vec4!(xy, zw)`, which builds a [`V4`] with `xy` and `zw` values that can be turned into `Expr>`. 709 | /// - `vec4!(xy, z, w)`, which builds a [`V4`] with `xy`, `z: T` and `w: T`. 710 | /// - `vec4!(x, y, z, w)`, which builds a [`V3`] for `x: T`, `y: T` and `z: T`. 711 | #[macro_export] 712 | macro_rules! vec4 { 713 | ($a:expr) => { 714 | todo!("vec4 cast operator missing"); 715 | }; 716 | 717 | ($xy:expr, $zw:expr) => {{ 718 | use $crate::types::Vec4 as _; 719 | $crate::expr::Expr::vec4(($crate::expr::Expr::from($xy), $crate::expr::Expr::from($zw))) 720 | }}; 721 | 722 | ($xy:expr, $z:expr, $w:expr) => {{ 723 | use $crate::types::Vec4 as _; 724 | $crate::expr::Expr::vec4(( 725 | $crate::expr::Expr::from($xy), 726 | $crate::expr::Expr::from($z), 727 | $crate::expr::Expr::from($w), 728 | )) 729 | }}; 730 | 731 | ($x:expr, $y:expr, $z:expr, $w:expr) => {{ 732 | use $crate::types::Vec4 as _; 733 | $crate::expr::Expr::vec4(( 734 | $crate::expr::Expr::from($x), 735 | $crate::expr::Expr::from($y), 736 | $crate::expr::Expr::from($z), 737 | $crate::expr::Expr::from($w), 738 | )) 739 | }}; 740 | } 741 | 742 | #[cfg(test)] 743 | mod test { 744 | use super::*; 745 | use crate::{ 746 | scope::{Scope, ScopeInstr}, 747 | stdlib::Bounded as _, 748 | types::{Dim, PrimType}, 749 | }; 750 | 751 | #[test] 752 | fn unary() { 753 | let mut scope = Scope::<()>::new(0); 754 | 755 | let a = !Expr::from(true); 756 | let b = -Expr::from(3i32); 757 | let c = scope.var(a.clone()); 758 | 759 | assert_eq!( 760 | a.erased, 761 | ErasedExpr::Not(Box::new(ErasedExpr::LitBool(true))) 762 | ); 763 | assert_eq!(b.erased, ErasedExpr::Neg(Box::new(ErasedExpr::LitInt(3)))); 764 | assert_eq!(c.erased, ErasedExpr::Var(ScopedHandle::fun_var(0, 0))); 765 | } 766 | 767 | #[test] 768 | fn binary() { 769 | let a = Expr::from(1i32) + Expr::from(2); 770 | 771 | assert_eq!( 772 | a.erased, 773 | ErasedExpr::Add( 774 | Box::new(ErasedExpr::LitInt(1)), 775 | Box::new(ErasedExpr::LitInt(2)), 776 | ) 777 | ); 778 | 779 | let a = Expr::from(1i32) - Expr::from(2); 780 | 781 | assert_eq!( 782 | a.erased, 783 | ErasedExpr::Sub( 784 | Box::new(ErasedExpr::LitInt(1)), 785 | Box::new(ErasedExpr::LitInt(2)), 786 | ) 787 | ); 788 | 789 | let a = Expr::from(1i32) * Expr::from(2); 790 | 791 | assert_eq!( 792 | a.erased, 793 | ErasedExpr::Mul( 794 | Box::new(ErasedExpr::LitInt(1)), 795 | Box::new(ErasedExpr::LitInt(2)), 796 | ) 797 | ); 798 | 799 | let a = Expr::from(1i32) / Expr::from(2); 800 | 801 | assert_eq!( 802 | a.erased, 803 | ErasedExpr::Div( 804 | Box::new(ErasedExpr::LitInt(1)), 805 | Box::new(ErasedExpr::LitInt(2)), 806 | ) 807 | ); 808 | } 809 | 810 | #[test] 811 | fn clone() { 812 | let a = Expr::from(1i32); 813 | let b = a.clone() + Expr::from(1); 814 | let c = a + Expr::from(1); 815 | 816 | assert_eq!(b.erased, c.erased); 817 | } 818 | 819 | #[test] 820 | fn var() { 821 | let mut scope = Scope::<()>::new(0); 822 | 823 | let x = scope.var(Expr::from(0)); 824 | let y = scope.var(Expr::from(1u32)); 825 | let z = scope.var(vec3![false, true, false]); 826 | 827 | assert_eq!(x.erased, ErasedExpr::Var(ScopedHandle::fun_var(0, 0))); 828 | assert_eq!(y.erased, ErasedExpr::Var(ScopedHandle::fun_var(0, 1))); 829 | assert_eq!( 830 | z.erased, 831 | ErasedExpr::Var(ScopedHandle::fun_var(0, 2).into()) 832 | ); 833 | assert_eq!(scope.erased.instructions.len(), 3); 834 | assert_eq!( 835 | scope.erased.instructions[0], 836 | ScopeInstr::VarDecl { 837 | ty: Type { 838 | prim_ty: PrimType::Int(Dim::Scalar), 839 | array_dims: Vec::new(), 840 | }, 841 | handle: ScopedHandle::fun_var(0, 0), 842 | init_value: ErasedExpr::LitInt(0), 843 | } 844 | ); 845 | assert_eq!( 846 | scope.erased.instructions[1], 847 | ScopeInstr::VarDecl { 848 | ty: Type { 849 | prim_ty: PrimType::UInt(Dim::Scalar), 850 | array_dims: Vec::new(), 851 | }, 852 | handle: ScopedHandle::fun_var(0, 1), 853 | init_value: ErasedExpr::LitUInt(1), 854 | } 855 | ); 856 | assert_eq!( 857 | scope.erased.instructions[2], 858 | ScopeInstr::VarDecl { 859 | ty: Type { 860 | prim_ty: PrimType::Bool(Dim::D3), 861 | array_dims: Vec::new(), 862 | }, 863 | handle: ScopedHandle::fun_var(0, 2), 864 | init_value: ErasedExpr::FunCall( 865 | ErasedFunHandle::Vec3, 866 | vec![ 867 | Expr::from(false).erased, 868 | Expr::from(true).erased, 869 | Expr::from(false).erased 870 | ] 871 | ) 872 | } 873 | ); 874 | } 875 | 876 | #[test] 877 | fn min_max_clamp() { 878 | let a = Expr::from(1i32); 879 | let b = Expr::from(2); 880 | let c = Expr::from(3); 881 | 882 | assert_eq!( 883 | a.clone().min(b.clone()).erased, 884 | ErasedExpr::FunCall( 885 | ErasedFunHandle::Min, 886 | vec![ErasedExpr::LitInt(1), ErasedExpr::LitInt(2)], 887 | ) 888 | ); 889 | 890 | assert_eq!( 891 | a.clone().max(b.clone()).erased, 892 | ErasedExpr::FunCall( 893 | ErasedFunHandle::Max, 894 | vec![ErasedExpr::LitInt(1), ErasedExpr::LitInt(2)], 895 | ) 896 | ); 897 | 898 | assert_eq!( 899 | a.clone().clamp(b, c).erased, 900 | ErasedExpr::FunCall( 901 | ErasedFunHandle::Clamp, 902 | vec![ 903 | ErasedExpr::LitInt(1), 904 | ErasedExpr::LitInt(2), 905 | ErasedExpr::LitInt(3) 906 | ], 907 | ) 908 | ); 909 | } 910 | 911 | #[test] 912 | fn array_creation() { 913 | let _ = Expr::from([1, 2, 3]); 914 | let _ = Expr::from(&[1, 2, 3]); 915 | let _ = Expr::from([vec2!(1., 2.)]); 916 | let two_d = Expr::from([[1, 2], [3, 4]]); 917 | 918 | assert_eq!( 919 | two_d.erased, 920 | ErasedExpr::Array( 921 | <[[i32; 2]; 2] as ToType>::ty(), 922 | vec![ 923 | ErasedExpr::Array( 924 | <[i32; 2] as ToType>::ty(), 925 | vec![ErasedExpr::LitInt(1), ErasedExpr::LitInt(2)] 926 | ), 927 | ErasedExpr::Array( 928 | <[i32; 2] as ToType>::ty(), 929 | vec![ErasedExpr::LitInt(3), ErasedExpr::LitInt(4)] 930 | ) 931 | ] 932 | ) 933 | ); 934 | } 935 | 936 | #[test] 937 | fn vec3_ctor() { 938 | let xy = vec2![1., 2.]; 939 | let xyz2 = vec3![xy.clone(), 3.]; 940 | let xyz3 = vec3![1., 2., 3.]; 941 | 942 | assert_eq!( 943 | xyz2.erased, 944 | ErasedExpr::FunCall( 945 | ErasedFunHandle::Vec3, 946 | vec![xy.erased, Expr::from(3.).erased] 947 | ) 948 | ); 949 | 950 | assert_eq!( 951 | xyz3.erased, 952 | ErasedExpr::FunCall( 953 | ErasedFunHandle::Vec3, 954 | vec![ 955 | Expr::from(1.).erased, 956 | Expr::from(2.).erased, 957 | Expr::from(3.).erased 958 | ] 959 | ) 960 | ); 961 | } 962 | 963 | #[test] 964 | fn vec4_ctor() { 965 | let xy = vec2![1., 2.]; 966 | let xyzw22 = vec4!(xy.clone(), xy.clone()); 967 | let xyzw211 = vec4!(xy.clone(), 3., 4.); 968 | let xyzw31 = vec4!(vec3!(1., 2., 3.), 4.); 969 | let xyzw4 = vec4!(1., 2., 3., 4.); 970 | 971 | assert_eq!( 972 | xyzw22.erased, 973 | ErasedExpr::FunCall( 974 | ErasedFunHandle::Vec4, 975 | vec![xy.clone().erased, xy.clone().erased] 976 | ) 977 | ); 978 | 979 | assert_eq!( 980 | xyzw211.erased, 981 | ErasedExpr::FunCall( 982 | ErasedFunHandle::Vec4, 983 | vec![ 984 | xy.clone().erased, 985 | Expr::from(3.).erased, 986 | Expr::from(4.).erased 987 | ] 988 | ) 989 | ); 990 | 991 | assert_eq!( 992 | xyzw31.erased, 993 | ErasedExpr::FunCall( 994 | ErasedFunHandle::Vec4, 995 | vec![vec3!(1., 2., 3.).erased, Expr::from(4.).erased] 996 | ) 997 | ); 998 | 999 | assert_eq!( 1000 | xyzw4.erased, 1001 | ErasedExpr::FunCall( 1002 | ErasedFunHandle::Vec4, 1003 | vec![ 1004 | Expr::from(1.).erased, 1005 | Expr::from(2.).erased, 1006 | Expr::from(3.).erased, 1007 | Expr::from(4.).erased 1008 | ] 1009 | ) 1010 | ); 1011 | } 1012 | } 1013 | -------------------------------------------------------------------------------- /shades/src/fun.rs: -------------------------------------------------------------------------------- 1 | //! Function definition, arguments, return and body. 2 | 3 | use crate::{ 4 | erased::Erased, 5 | expr::{ErasedExpr, Expr}, 6 | scope::ErasedScope, 7 | types::{ToType, Type}, 8 | }; 9 | use std::marker::PhantomData; 10 | 11 | /// Function return. 12 | /// 13 | /// This type represents a function return and is used to annotate values that can be returned from functions (i.e. 14 | /// expressions). 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub struct Return { 17 | pub(crate) erased: ErasedReturn, 18 | } 19 | 20 | /// Erased return. 21 | /// 22 | /// Either `Void` (i.e. `void`) or an expression. The type of the expression is also present for convenience. 23 | #[derive(Clone, Debug, PartialEq)] 24 | pub enum ErasedReturn { 25 | Void, 26 | Expr(Type, ErasedExpr), 27 | } 28 | 29 | impl From<()> for Return { 30 | fn from(_: ()) -> Self { 31 | Return { 32 | erased: ErasedReturn::Void, 33 | } 34 | } 35 | } 36 | 37 | impl From> for Return 38 | where 39 | T: ToType, 40 | { 41 | fn from(expr: Expr) -> Self { 42 | Return { 43 | erased: ErasedReturn::Expr(T::ty(), expr.erased), 44 | } 45 | } 46 | } 47 | 48 | /// An opaque function handle, used to call user-defined functions. 49 | /// 50 | /// Function handles are created with the [`StageBuilder::fun`] function, introducing new functions in the EDSL. You 51 | /// can then call the functions in the context of generating new expressions, returning them or creating variables. 52 | /// 53 | /// Injecting a function call in the EDSL is done via two current mechanisms: 54 | #[derive(Clone, Debug, PartialEq)] 55 | pub struct FunHandle { 56 | pub(crate) erased: ErasedFunHandle, 57 | _phantom: PhantomData<(R, A)>, 58 | } 59 | 60 | impl Erased for FunHandle { 61 | type Erased = ErasedFunHandle; 62 | 63 | fn to_erased(self) -> Self::Erased { 64 | self.erased 65 | } 66 | 67 | fn erased(&self) -> &Self::Erased { 68 | &self.erased 69 | } 70 | 71 | fn erased_mut(&mut self) -> &mut Self::Erased { 72 | &mut self.erased 73 | } 74 | } 75 | 76 | impl FunHandle { 77 | pub(crate) fn new(erased: ErasedFunHandle) -> Self { 78 | Self { 79 | erased, 80 | _phantom: PhantomData, 81 | } 82 | } 83 | } 84 | 85 | impl FunHandle, ()> { 86 | /// Create an expression representing a function call to this function. 87 | /// 88 | /// See the documentation of [`FunHandle`] for examples. 89 | pub fn call2(&self, args: Vec) -> Expr { 90 | Expr::new(ErasedExpr::FunCall(self.erased.clone(), args)) 91 | } 92 | } 93 | 94 | impl FunHandle, ()> { 95 | /// Create an expression representing a function call to this function. 96 | /// 97 | /// See the documentation of [`FunHandle`] for examples. 98 | pub fn call(&self) -> Expr { 99 | Expr::new(ErasedExpr::FunCall(self.erased.clone(), Vec::new())) 100 | } 101 | } 102 | 103 | impl FunHandle, Expr> { 104 | /// Create an expression representing a function call to this function. 105 | /// 106 | /// See the documentation of [`FunHandle`] for examples. 107 | pub fn call(&self, a: Expr) -> Expr { 108 | Expr::new(ErasedExpr::FunCall(self.erased.clone(), vec![a.erased])) 109 | } 110 | } 111 | 112 | // the first stage must be named S0 113 | macro_rules! impl_FunCall { 114 | ( $( ( $arg_name:ident, $arg_ty:ident ) ),*) => { 115 | impl FunHandle, ($(Expr<$arg_ty>),*)> 116 | { 117 | /// Create an expression representing a function call to this function. 118 | /// 119 | /// See the documentation of [`FunHandle`] for examples. 120 | pub fn call(&self, $($arg_name : Expr<$arg_ty>),*) -> Expr { 121 | Expr::new(ErasedExpr::FunCall(self.erased.clone(), vec![$($arg_name.erased),*])) 122 | } 123 | } 124 | }; 125 | } 126 | 127 | // implement function calls for Expr up to 16 arguments 128 | macro_rules! impl_FunCall_rec { 129 | ( ( $a:ident, $b:ident ) , ( $x:ident, $y:ident )) => { 130 | impl_FunCall!(($a, $b), ($x, $y)); 131 | }; 132 | 133 | ( ( $a:ident, $b:ident ) , ( $x: ident, $y: ident ) , $($r:tt)* ) => { 134 | impl_FunCall_rec!(($a, $b), $($r)*); 135 | impl_FunCall!(($a, $b), ($x, $y), $($r)*); 136 | }; 137 | } 138 | impl_FunCall_rec!( 139 | (a, A), 140 | (b, B), 141 | (c, C), 142 | (d, D), 143 | (e, E), 144 | (f, F), 145 | (g, G), 146 | (h, H), 147 | (i, I), 148 | (j, J), 149 | (k, K), 150 | (l, L), 151 | (m, M), 152 | (n, N), 153 | (o, O), 154 | (p, P) 155 | ); 156 | 157 | /// Erased function handle. 158 | #[derive(Clone, Debug, PartialEq)] 159 | pub enum ErasedFunHandle { 160 | // cast operators 161 | Vec2, 162 | Vec3, 163 | Vec4, 164 | // trigonometry 165 | Radians, 166 | Degrees, 167 | Sin, 168 | Cos, 169 | Tan, 170 | ASin, 171 | ACos, 172 | ATan, 173 | SinH, 174 | CosH, 175 | TanH, 176 | ASinH, 177 | ACosH, 178 | ATanH, 179 | // exponential 180 | Pow, 181 | Exp, 182 | Exp2, 183 | Log, 184 | Log2, 185 | Sqrt, 186 | InverseSqrt, 187 | // common 188 | Abs, 189 | Sign, 190 | Floor, 191 | Trunc, 192 | Round, 193 | RoundEven, 194 | Ceil, 195 | Fract, 196 | Min, 197 | Max, 198 | Clamp, 199 | Mix, 200 | Step, 201 | SmoothStep, 202 | IsNan, 203 | IsInf, 204 | FloatBitsToInt, 205 | IntBitsToFloat, 206 | UIntBitsToFloat, 207 | FMA, 208 | Frexp, 209 | Ldexp, 210 | // floating-point pack and unpack functions 211 | PackUnorm2x16, 212 | PackSnorm2x16, 213 | PackUnorm4x8, 214 | PackSnorm4x8, 215 | UnpackUnorm2x16, 216 | UnpackSnorm2x16, 217 | UnpackUnorm4x8, 218 | UnpackSnorm4x8, 219 | PackHalf2x16, 220 | UnpackHalf2x16, 221 | // geometry functions 222 | Length, 223 | Distance, 224 | Dot, 225 | Cross, 226 | Normalize, 227 | FaceForward, 228 | Reflect, 229 | Refract, 230 | // matrix functions 231 | // TODO 232 | // vector relational functions 233 | VLt, 234 | VLte, 235 | VGt, 236 | VGte, 237 | VEq, 238 | VNeq, 239 | VAny, 240 | VAll, 241 | VNot, 242 | // integer functions 243 | UAddCarry, 244 | USubBorrow, 245 | UMulExtended, 246 | IMulExtended, 247 | BitfieldExtract, 248 | BitfieldInsert, 249 | BitfieldReverse, 250 | BitCount, 251 | FindLSB, 252 | FindMSB, 253 | // texture functions 254 | // TODO 255 | // geometry shader functions 256 | EmitStreamVertex, 257 | EndStreamPrimitive, 258 | EmitVertex, 259 | EndPrimitive, 260 | // fragment processing functions 261 | DFDX, 262 | DFDY, 263 | DFDXFine, 264 | DFDYFine, 265 | DFDXCoarse, 266 | DFDYCoarse, 267 | FWidth, 268 | FWidthFine, 269 | FWidthCoarse, 270 | InterpolateAtCentroid, 271 | InterpolateAtSample, 272 | InterpolateAtOffset, 273 | // shader invocation control functions 274 | Barrier, 275 | MemoryBarrier, 276 | MemoryBarrierAtomic, 277 | MemoryBarrierBuffer, 278 | MemoryBarrierShared, 279 | MemoryBarrierImage, 280 | GroupMemoryBarrier, 281 | // shader invocation group functions 282 | AnyInvocation, 283 | AllInvocations, 284 | AllInvocationsEqual, 285 | UserDefined(u16), 286 | } 287 | 288 | /// A function definition. 289 | /// 290 | /// Function definitions contain the information required to know how to represent a function’s arguments, return type 291 | /// and its body. 292 | #[derive(Debug)] 293 | pub struct FunDef { 294 | pub(crate) erased: ErasedFun, 295 | _phantom: PhantomData<(R, A)>, 296 | } 297 | 298 | impl FunDef { 299 | pub fn new(erased: ErasedFun) -> Self { 300 | Self { 301 | erased, 302 | _phantom: PhantomData, 303 | } 304 | } 305 | } 306 | 307 | /// Erased function definition. 308 | #[derive(Debug)] 309 | pub struct ErasedFun { 310 | pub(crate) args: Vec, 311 | pub(crate) ret_ty: Option, 312 | pub(crate) scope: ErasedScope, 313 | } 314 | 315 | impl ErasedFun { 316 | pub fn new(args: Vec, ret_ty: Option, scope: ErasedScope) -> Self { 317 | Self { 318 | args, 319 | ret_ty, 320 | scope, 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /shades/src/input.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::{ 4 | builtin::{ 5 | BuiltIn, FragmentBuiltIn, GeometryBuiltIn, TessCtrlBuiltIn, TessEvalBuiltIn, VertexBuiltIn, 6 | }, 7 | expr::{ErasedExpr, Expr}, 8 | types::{Type, V2, V3, V4}, 9 | }; 10 | 11 | /// Inputs. 12 | pub trait Inputs { 13 | type In; 14 | 15 | fn input() -> Self::In; 16 | fn input_set() -> Vec<(u16, Type)>; 17 | } 18 | 19 | impl Inputs for () { 20 | type In = (); 21 | 22 | fn input() -> Self::In { 23 | () 24 | } 25 | 26 | fn input_set() -> Vec<(u16, Type)> { 27 | Vec::new() 28 | } 29 | } 30 | 31 | /// Vertex shader environment inputs. 32 | #[derive(Debug)] 33 | pub struct VertexShaderInputs { 34 | /// ID of the current vertex. 35 | pub vertex_id: Expr, 36 | 37 | /// Instance ID of the current vertex. 38 | pub instance_id: Expr, 39 | 40 | /// Base vertex offset. 41 | pub base_vertex: Expr, 42 | 43 | /// Base instance vertex offset. 44 | pub base_instance: Expr, 45 | 46 | // User-defined inputs. 47 | pub user: I, 48 | } 49 | 50 | impl VertexShaderInputs { 51 | pub(crate) const fn new(user: I) -> Self { 52 | let vertex_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 53 | VertexBuiltIn::VertexID, 54 | ))); 55 | let instance_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 56 | VertexBuiltIn::InstanceID, 57 | ))); 58 | let base_vertex = Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 59 | VertexBuiltIn::BaseVertex, 60 | ))); 61 | let base_instance = Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 62 | VertexBuiltIn::BaseInstance, 63 | ))); 64 | 65 | Self { 66 | vertex_id, 67 | instance_id, 68 | base_vertex, 69 | base_instance, 70 | user, 71 | } 72 | } 73 | } 74 | 75 | impl Deref for VertexShaderInputs { 76 | type Target = I; 77 | 78 | fn deref(&self) -> &Self::Target { 79 | &self.user 80 | } 81 | } 82 | 83 | /// Tessellation control shader inputs. 84 | #[derive(Debug)] 85 | pub struct TessCtrlShaderInputs { 86 | /// Maximum number of vertices per patch. 87 | pub max_patch_vertices_in: Expr, 88 | 89 | /// Number of vertices for the current patch. 90 | pub patch_vertices_in: Expr, 91 | 92 | /// ID of the current primitive. 93 | pub primitive_id: Expr, 94 | 95 | /// ID of the current tessellation control shader invocation. 96 | pub invocation_id: Expr, 97 | 98 | /// Array of per-vertex input expressions. 99 | pub input: Expr<[TessControlPerVertexIn]>, 100 | 101 | pub user: I, 102 | } 103 | 104 | impl TessCtrlShaderInputs { 105 | pub(crate) const fn new(user: I) -> Self { 106 | let max_patch_vertices_in = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 107 | TessCtrlBuiltIn::MaxPatchVerticesIn, 108 | ))); 109 | let patch_vertices_in = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 110 | TessCtrlBuiltIn::PatchVerticesIn, 111 | ))); 112 | let primitive_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 113 | TessCtrlBuiltIn::PrimitiveID, 114 | ))); 115 | let invocation_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 116 | TessCtrlBuiltIn::InvocationID, 117 | ))); 118 | let input = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 119 | TessCtrlBuiltIn::In, 120 | ))); 121 | 122 | Self { 123 | max_patch_vertices_in, 124 | patch_vertices_in, 125 | primitive_id, 126 | invocation_id, 127 | input, 128 | user, 129 | } 130 | } 131 | } 132 | 133 | impl Deref for TessCtrlShaderInputs { 134 | type Target = I; 135 | 136 | fn deref(&self) -> &Self::Target { 137 | &self.user 138 | } 139 | } 140 | 141 | /// Read-only, input tessellation control shader environment. 142 | #[derive(Debug)] 143 | pub struct TessControlPerVertexIn; 144 | 145 | impl Expr { 146 | pub fn position(&self) -> Expr> { 147 | let erased = ErasedExpr::Field { 148 | object: Box::new(self.erased.clone()), 149 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 150 | TessCtrlBuiltIn::Position, 151 | ))), 152 | }; 153 | 154 | Expr::new(erased) 155 | } 156 | 157 | pub fn point_size(&self) -> Expr { 158 | let erased = ErasedExpr::Field { 159 | object: Box::new(self.erased.clone()), 160 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 161 | TessCtrlBuiltIn::PointSize, 162 | ))), 163 | }; 164 | 165 | Expr::new(erased) 166 | } 167 | 168 | pub fn clip_distance(&self) -> Expr<[f32]> { 169 | let erased = ErasedExpr::Field { 170 | object: Box::new(self.erased.clone()), 171 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 172 | TessCtrlBuiltIn::ClipDistance, 173 | ))), 174 | }; 175 | 176 | Expr::new(erased) 177 | } 178 | 179 | pub fn cull_distance(&self) -> Expr<[f32]> { 180 | let erased = ErasedExpr::Field { 181 | object: Box::new(self.erased.clone()), 182 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 183 | TessCtrlBuiltIn::CullDistance, 184 | ))), 185 | }; 186 | 187 | Expr::new(erased) 188 | } 189 | } 190 | 191 | /// Tessellation evalution shader inputs. 192 | #[derive(Debug)] 193 | pub struct TessEvalShaderInputs { 194 | /// Number of vertices in the current patch. 195 | pub patch_vertices_in: Expr, 196 | 197 | /// ID of the current primitive. 198 | pub primitive_id: Expr, 199 | 200 | /// Tessellation coordinates of the current vertex. 201 | pub tess_coord: Expr>, 202 | 203 | /// Outer tessellation levels. 204 | pub tess_level_outer: Expr<[f32; 4]>, 205 | 206 | /// Inner tessellation levels. 207 | pub tess_level_inner: Expr<[f32; 2]>, 208 | 209 | /// Array of per-evertex expressions. 210 | pub input: Expr<[TessEvaluationPerVertexIn]>, 211 | 212 | pub user: I, 213 | } 214 | 215 | impl TessEvalShaderInputs { 216 | pub(crate) const fn new(user: I) -> Self { 217 | let patch_vertices_in = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 218 | TessEvalBuiltIn::PatchVerticesIn, 219 | ))); 220 | let primitive_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 221 | TessEvalBuiltIn::PrimitiveID, 222 | ))); 223 | let tess_coord = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 224 | TessEvalBuiltIn::TessCoord, 225 | ))); 226 | let tess_level_outer = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 227 | TessEvalBuiltIn::TessellationLevelOuter, 228 | ))); 229 | let tess_level_inner = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 230 | TessEvalBuiltIn::TessellationLevelInner, 231 | ))); 232 | let input = Expr::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 233 | TessEvalBuiltIn::In, 234 | ))); 235 | 236 | Self { 237 | patch_vertices_in, 238 | primitive_id, 239 | tess_coord, 240 | tess_level_outer, 241 | tess_level_inner, 242 | input, 243 | user, 244 | } 245 | } 246 | } 247 | 248 | impl Deref for TessEvalShaderInputs { 249 | type Target = I; 250 | 251 | fn deref(&self) -> &Self::Target { 252 | &self.user 253 | } 254 | } 255 | 256 | /// Tessellation evaluation per-vertex expression. 257 | #[derive(Debug)] 258 | pub struct TessEvaluationPerVertexIn; 259 | 260 | impl Expr { 261 | /// 4D position of the vertex. 262 | pub fn position(&self) -> Expr> { 263 | let erased = ErasedExpr::Field { 264 | object: Box::new(self.erased.clone()), 265 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 266 | TessEvalBuiltIn::Position, 267 | ))), 268 | }; 269 | 270 | Expr::new(erased) 271 | } 272 | 273 | /// Point size of the vertex. 274 | pub fn point_size(&self) -> Expr { 275 | let erased = ErasedExpr::Field { 276 | object: Box::new(self.erased.clone()), 277 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 278 | TessEvalBuiltIn::PointSize, 279 | ))), 280 | }; 281 | 282 | Expr::new(erased) 283 | } 284 | 285 | /// Clip distances to user-defined planes. 286 | pub fn clip_distance(&self) -> Expr<[f32]> { 287 | let erased = ErasedExpr::Field { 288 | object: Box::new(self.erased.clone()), 289 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 290 | TessEvalBuiltIn::ClipDistance, 291 | ))), 292 | }; 293 | 294 | Expr::new(erased) 295 | } 296 | 297 | /// Cull distances to user-defined planes. 298 | pub fn cull_distance(&self) -> Expr<[f32]> { 299 | let erased = ErasedExpr::Field { 300 | object: Box::new(self.erased.clone()), 301 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessEval( 302 | TessEvalBuiltIn::CullDistance, 303 | ))), 304 | }; 305 | 306 | Expr::new(erased) 307 | } 308 | } 309 | 310 | /// Geometry shader inputs. 311 | #[derive(Debug)] 312 | pub struct GeometryShaderInputs { 313 | /// Contains the index of the current primitive. 314 | pub primitive_id_in: Expr, 315 | 316 | /// ID of the current invocation of the geometry shader. 317 | pub invocation_id: Expr, 318 | 319 | /// Read-only environment for each vertices. 320 | pub input: Expr<[GeometryPerVertexIn]>, 321 | 322 | pub user: I, 323 | } 324 | 325 | impl GeometryShaderInputs { 326 | pub(crate) const fn new(user: I) -> Self { 327 | let primitive_id_in = Expr::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 328 | GeometryBuiltIn::PrimitiveIDIn, 329 | ))); 330 | let invocation_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 331 | GeometryBuiltIn::InvocationID, 332 | ))); 333 | let input = Expr::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 334 | GeometryBuiltIn::In, 335 | ))); 336 | 337 | Self { 338 | primitive_id_in, 339 | invocation_id, 340 | input, 341 | user, 342 | } 343 | } 344 | } 345 | 346 | impl Deref for GeometryShaderInputs { 347 | type Target = I; 348 | 349 | fn deref(&self) -> &Self::Target { 350 | &self.user 351 | } 352 | } 353 | 354 | /// Read-only, input geometry shader environment. 355 | #[derive(Debug)] 356 | pub struct GeometryPerVertexIn; 357 | 358 | impl Expr { 359 | /// Provides 4D the position of the vertex. 360 | pub fn position(&self) -> Expr> { 361 | let erased = ErasedExpr::Field { 362 | object: Box::new(self.erased.clone()), 363 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 364 | GeometryBuiltIn::Position, 365 | ))), 366 | }; 367 | 368 | Expr::new(erased) 369 | } 370 | 371 | /// Provides the size point of the vertex if it’s currently being rendered in point mode. 372 | pub fn point_size(&self) -> Expr { 373 | let erased = ErasedExpr::Field { 374 | object: Box::new(self.erased.clone()), 375 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 376 | GeometryBuiltIn::PointSize, 377 | ))), 378 | }; 379 | 380 | Expr::new(erased) 381 | } 382 | 383 | /// Clip distances to user planes of the vertex. 384 | pub fn clip_distance(&self) -> Expr<[f32]> { 385 | let erased = ErasedExpr::Field { 386 | object: Box::new(self.erased.clone()), 387 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 388 | GeometryBuiltIn::ClipDistance, 389 | ))), 390 | }; 391 | 392 | Expr::new(erased) 393 | } 394 | 395 | /// Cull distances to user planes of the vertex. 396 | pub fn cull_distance(&self) -> Expr<[f32]> { 397 | let erased = ErasedExpr::Field { 398 | object: Box::new(self.erased.clone()), 399 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::Geometry( 400 | GeometryBuiltIn::CullDistance, 401 | ))), 402 | }; 403 | 404 | Expr::new(erased) 405 | } 406 | } 407 | 408 | /// Fragment shader inputs. 409 | /// 410 | /// This type contains everything you have access to when writing a fragment shader. 411 | #[derive(Debug)] 412 | pub struct FragmentShaderInputs { 413 | /// Fragment coordinate in the framebuffer. 414 | pub frag_coord: Expr>, 415 | 416 | /// Whether the fragment is front-facing. 417 | pub front_facing: Expr, 418 | 419 | /// Clip distances to user planes. 420 | /// 421 | /// This is an array giving the clip distances to each of the user clip planes. 422 | pub clip_distance: Expr<[f32]>, 423 | 424 | /// Cull distances to user planes. 425 | /// 426 | /// This is an array giving the cull distances to each of the user clip planes. 427 | pub cull_distance: Expr<[f32]>, 428 | 429 | /// Contains the 2D coordinates of a fragment within a point primitive. 430 | pub point_coord: Expr>, 431 | 432 | /// ID of the primitive being currently rendered. 433 | pub primitive_id: Expr, 434 | 435 | /// ID of the sample being currently rendered. 436 | pub sample_id: Expr, 437 | 438 | /// Sample 2D coordinates. 439 | pub sample_position: Expr>, 440 | 441 | /// Contains the computed sample coverage mask for the current fragment. 442 | pub sample_mask_in: Expr, 443 | 444 | /// Layer the fragment will be written to. 445 | pub layer: Expr, 446 | 447 | /// Viewport index the fragment will be written to. 448 | pub viewport_index: Expr, 449 | 450 | /// Indicates whether we are in a helper invocation of a fragment shader. 451 | pub helper_invocation: Expr, 452 | 453 | pub user: I, 454 | } 455 | 456 | impl FragmentShaderInputs { 457 | pub(crate) const fn new(user: I) -> Self { 458 | let frag_coord = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 459 | FragmentBuiltIn::FragCoord, 460 | ))); 461 | let front_facing = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 462 | FragmentBuiltIn::FrontFacing, 463 | ))); 464 | let clip_distance = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 465 | FragmentBuiltIn::ClipDistance, 466 | ))); 467 | let cull_distance = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 468 | FragmentBuiltIn::CullDistance, 469 | ))); 470 | let point_coord = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 471 | FragmentBuiltIn::PointCoord, 472 | ))); 473 | let primitive_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 474 | FragmentBuiltIn::PrimitiveID, 475 | ))); 476 | let sample_id = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 477 | FragmentBuiltIn::SampleID, 478 | ))); 479 | let sample_position = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 480 | FragmentBuiltIn::SamplePosition, 481 | ))); 482 | let sample_mask_in = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 483 | FragmentBuiltIn::SampleMaskIn, 484 | ))); 485 | let layer = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 486 | FragmentBuiltIn::Layer, 487 | ))); 488 | let viewport_index = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 489 | FragmentBuiltIn::ViewportIndex, 490 | ))); 491 | let helper_invocation = Expr::new(ErasedExpr::new_builtin(BuiltIn::Fragment( 492 | FragmentBuiltIn::HelperInvocation, 493 | ))); 494 | 495 | Self { 496 | frag_coord, 497 | front_facing, 498 | clip_distance, 499 | cull_distance, 500 | point_coord, 501 | primitive_id, 502 | sample_id, 503 | sample_position, 504 | sample_mask_in, 505 | layer, 506 | viewport_index, 507 | helper_invocation, 508 | user, 509 | } 510 | } 511 | } 512 | 513 | impl Deref for FragmentShaderInputs { 514 | type Target = I; 515 | 516 | fn deref(&self) -> &Self::Target { 517 | &self.user 518 | } 519 | } 520 | 521 | #[cfg(test)] 522 | mod test { 523 | use super::*; 524 | 525 | #[test] 526 | fn vertex_id_commutative() { 527 | let vertex = VertexShaderInputs::new(()); 528 | 529 | let x = Expr::from(1); 530 | let _ = vertex.vertex_id.clone() + x.clone(); 531 | let _ = x + vertex.vertex_id; 532 | } 533 | } 534 | -------------------------------------------------------------------------------- /shades/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Shades, a shading language EDSL in vanilla Rust. 2 | //! 3 | //! This crate provides an [EDSL] to build [shaders], leveraging the Rust compiler (`rustc`) and its type system to ensure 4 | //! soundness and typing. Because shaders are written in Rust, this crate is completely language agnostic: it can in theory 5 | //! target any shading language – the current tier-1 language being [GLSL]. The EDSL allows to statically type shaders 6 | //! while still generating the actual shading code at runtime. 7 | //! 8 | //! # Motivation 9 | //! 10 | //! In typical graphics libraries and engines, shaders are _opaque strings_ – either hard-coded in the program, read from 11 | //! a file at runtime, constructed via fragments of strings concatenated with each others, etc. The strings are passed to 12 | //! the graphics drivers, which will _compile_ and _link_ the code at runtime. It is the responsibility of the runtime 13 | //! (i.e. the graphics library, engine or the application) to check for errors and react correctly. Shading languages can 14 | //! also be compiled _off-line_, and their bytecode is then used at runtime (c.f. SPIR-V). 15 | //! 16 | //! For a lot of people, that has proven okay for decades and even allowed _live coding_: because the shading code is 17 | //! loaded at runtime, it is possible to re-load, re-compile and re-link it every time a change happens. However, this comes 18 | //! with non-negligible drawbacks: 19 | //! 20 | //! - The shading code is often checked at runtime. In this case, ill-written shaders won’t be visible by programmers until 21 | //! the runtime is executed and the GPU driver refuses the shading code. 22 | //! - When compiled off-line and transpiled to bytecode, extra specialized tooling is required (such as an external program, 23 | //! a language extension, etc.). 24 | //! - Writing shaders implies learning a new language. The most widespread shading language is [GLSL] but others exist, 25 | //! meaning that people will have to learn specialized languages and, most of the time, weaker compilation systems. For 26 | //! instance, [GLSL] doesn’t have anything natively to include other [GLSL] files and it’s an old C-like language. 27 | //! - Even though the appeal of using a language in a dynamic way can seem appealing, going from a dynamic language and 28 | //! using it in a statically manner is not an easy task. However, going the other way around (from a static to dynamic) 29 | //! is much much simpler. In other terms: it is possible to live-reload a compiled language with the help of low-level 30 | //! system primitives, such as `dlopen`, `dlsym`, etc. It’s more work but it’s possible. And 31 | //! [Rust can do it too](https://crates.io/crates/libloading). 32 | //! 33 | //! The author ([@phaazon]) of this crate thinks that shading code is still code, and that it should be treated as such. 34 | //! It’s easy to see the power of live-coding / reloading, but it’s more important to provide a shading code that is 35 | //! statically proven sound and with less bugs that without the static check. Also, as stated above, using a compiled 36 | //! approach doesn’t prevent from writing a relocatable object, compiled isolated and reloaded at runtime, providing 37 | //! roughly the same functionality as live-coding. 38 | //! 39 | //! Another important point is the choice of using an EDSL. Some people would argue that Rust has other interesting and 40 | //! powerful ways to achieve the same goal. It is important to notice that this crate doesn’t provide a compiler to compile 41 | //! Rust code to a shading language. Instead, it provides a Rust crate that will still generate the shading code at runtime. 42 | //! Several crates following a different approach: 43 | //! 44 | //! - You can use the [glsl](https://crates.io/crates/glsl) and [glsl-quasiquote](https://crates.io/crates/glsl-quasiquote) 45 | //! crates. The first one is a parser for GLSL and the second one allows you to write GLSL in a quasi-quoter 46 | //! (`glsl! { /* here */ }`) and get it compiled and checked at runtime. It’s still [GLSL], though, and the 47 | //! possibilities of runtime combinations are much less than an EDSL. Also, [glsl] doesn’t provide semantic analysis, 48 | //! so you are left implementing that on your own (and it’s a lot of work). 49 | //! - You can use the [rust-gpu] project. It’s a similar project but they use a `rustc` toolchain, compiling Rust code 50 | //! representing GPU code. It requires a specific toolchain and doesn’t operate at the same level of this crate — it can 51 | //! even compile a large part of the `core` library. 52 | //! 53 | //! ## Influences 54 | //! 55 | //! - [blaze-html], a [Haskell] [EDSL] to build HTML in [Haskell]. 56 | //! - [selda], a [Haskell] [EDSL] to build SQL queries and execute them without writing SQL strings. This current crate is 57 | //! very similar in the approach. 58 | //! 59 | //! # The AST crate and the EDSL crate 60 | //! 61 | //! This crate ([shades]) is actually half of the solution. [shades] provides the AST code. Using only [shades] requires 62 | //! you to learn all the types that represent a shading language AST. It can be tedious and counter-productive. For 63 | //! instance, writing a simple loop with [shades] requires several function calls and a weird syntax. 64 | //! 65 | //! Up to version `shades-0.3.6`, you didn’t really have a choice and you had to use that weird syntax. Some macros were 66 | //! available to simplify things, along with nightly extensions to call functions and declare them by hacking around 67 | //! lambdas. However, starting from `shades-0.4`, a new crate appeared: [shades-edsl]. 68 | //! 69 | //! [shades-edsl] solves the above problem by removing the “friendly” interface from [shades], reserving [shades]’ 70 | //! interface for being called by [shades-edsl] (or adventurous users!). [shades-edsl] is a procedural macro parsing 71 | //! regular Rust code and generating Rust code using [shades]. For instance, a function definition using [shades] only 72 | //! requires you to know about `FunDef`, `ErasedFunDef`, `Return`, `ErasedReturn`, how arguments are represented, etc. 73 | //! It can be very frustrating for people who just want to write the code. With [shades-edsl]? It’s as simple as: 74 | //! 75 | //! ```rust 76 | //! fn add(a: i32, b: i32) -> i32 { 77 | //! a + b 78 | //! } 79 | //! ``` 80 | //! 81 | //! If that function definiton is in a `shades!` block, it will be completely changed to use [shades] instead. 82 | //! 83 | //! # Why you would love this 84 | //! 85 | //! If you like type systems, languages and basically hacking compilers (writing code for your compiler to generate the 86 | //! runtime code!), then it’s likely you’ll like this crate. Among all the features you will find: 87 | //! 88 | //! - Use vanilla Rust. Because this crate is language-agnostic, the whole thing you need to know to get started is to 89 | //! write Rust. You don’t have to learn [GLSL] to use this crate — even though you still need to understand the concept 90 | //! of shaders, what they are, how they work, etc. But the _encoding of those concepts_ is now encapsulated by a native 91 | //! Rust crate. 92 | //! - Types used to represent shading types are basic and native Rust types, such as `bool`, `f32` or `[T; N]`. 93 | //! - Write a more functional code rather than imperative code! 94 | //! - Catch semantic bugs within `rustc`. For instance, assigning a `bool` to a `f32` in your shader code will trigger a 95 | //! `rustc` error, so that kind of errors won’t leak to your runtime. 96 | //! - Make some code impossible to write. For instance, you will not be able to use in a _vertex shader_ expressions only 97 | //! valid in the context of a _fragment shader_, as this is not possible by their own definitions. 98 | //! - Extend and add more items to famous shading languages. For instance, [GLSL] doesn’t have a `π` constant. This 99 | //! situation is fixed so you will never have to write `π` decimals by yourself anymore. 100 | //! - Because you write Rust, benefit from all the language type candies, composability, extensibility and soundness. 101 | //! - Using the proc-macro EDSL, you write shading code without even knowing it, since that crate reinterprets Rust code 102 | //! into AST nodes. You will not have to care about all the types and functions defined in this crate! You want to 103 | //! create a function in the shading language? You don’t have to use `FunDef`, simply use a regular `fn` Rust 104 | //! function. The EDSL does the rest. 105 | //! 106 | //! # Why you wouldn’t love this 107 | //! 108 | //! The crate is, as of nowadays, still very experimental. Here’s a list of things you might dislike about the crate: 109 | //! 110 | //! - Some people would argue that writing [GLSL] is much faster and simpler to write, and they would be partially right. 111 | //! However, you would need to learn [GLSL] in the first place; you wouldn’t be able to target SPIR-V; you wouldn’t have 112 | //! a solution to the static typing problem; etc. 113 | //! - In the case of a runtime compilation / linking failure of your shading code, debugging it might be challenging, as 114 | //! all the identifiers (with a few exceptions) are generated for you. It’ll make it harder to understand the generated 115 | //! code. 116 | //! 117 | //! [@phaazon]: https://github.com/phaazon 118 | //! [EDSL]: https://en.wikipedia.org/wiki/Domain-specific_language#External_and_Embedded_Domain_Specific_Languages 119 | //! [shaders]: https://en.wikipedia.org/wiki/Shader 120 | //! [GLSL]: https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf 121 | //! [Haskell]: https://www.haskell.org 122 | //! [blaze-html]: http://hackage.haskell.org/package/blaze-html 123 | //! [selda]: http://hackage.haskell.org/package/selda 124 | //! [proc-macro]: https://doc.rust-lang.org/reference/procedural-macros.html 125 | //! [rust-gpu]: https://github.com/EmbarkStudios/rust-gpu 126 | //! [do-notation]: https://crates.io/crates/do-notation 127 | //! [shades]: https://crates.io/crates/shades 128 | //! [shades-edsl]: https://crates.io/crates/shades-edsl 129 | 130 | pub mod builtin; 131 | pub mod env; 132 | pub mod erased; 133 | pub mod expr; 134 | pub mod fun; 135 | pub mod input; 136 | pub mod output; 137 | pub mod scope; 138 | pub mod shader; 139 | pub mod stage; 140 | pub mod stdlib; 141 | pub mod swizzle; 142 | pub mod types; 143 | pub mod var; 144 | pub mod writer; 145 | -------------------------------------------------------------------------------- /shades/src/output.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::{ 4 | builtin::{ 5 | BuiltIn, FragmentBuiltIn, GeometryBuiltIn, TessCtrlBuiltIn, TessEvalBuiltIn, VertexBuiltIn, 6 | }, 7 | expr::ErasedExpr, 8 | expr::Expr, 9 | scope::ScopedHandle, 10 | types::{Type, V4}, 11 | var::Var, 12 | }; 13 | 14 | /// Outputs. 15 | pub trait Outputs { 16 | type Out; 17 | 18 | fn output() -> Self::Out; 19 | 20 | fn output_set() -> Vec<(u16, Type)>; 21 | } 22 | 23 | impl Outputs for () { 24 | type Out = (); 25 | 26 | fn output() -> Self::Out { 27 | () 28 | } 29 | 30 | fn output_set() -> Vec<(u16, Type)> { 31 | Vec::new() 32 | } 33 | } 34 | 35 | /// Vertex shader environment outputs. 36 | #[derive(Debug)] 37 | pub struct VertexShaderOutputs { 38 | /// 4D position of the vertex. 39 | pub position: Var>, 40 | 41 | /// Point size of the vertex. 42 | pub point_size: Var, 43 | 44 | // Clip distances to user-defined plans. 45 | pub clip_distance: Var<[f32]>, 46 | 47 | // User-defined outputs. 48 | pub user: O, 49 | } 50 | 51 | impl VertexShaderOutputs { 52 | pub(crate) const fn new(user: O) -> Self { 53 | let position = Var(Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 54 | VertexBuiltIn::Position, 55 | )))); 56 | let point_size = Var(Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 57 | VertexBuiltIn::PointSize, 58 | )))); 59 | let clip_distance = Var(Expr::new(ErasedExpr::new_builtin(BuiltIn::Vertex( 60 | VertexBuiltIn::ClipDistance, 61 | )))); 62 | 63 | Self { 64 | position, 65 | point_size, 66 | clip_distance, 67 | user, 68 | } 69 | } 70 | } 71 | 72 | impl Deref for VertexShaderOutputs { 73 | type Target = O; 74 | 75 | fn deref(&self) -> &Self::Target { 76 | &self.user 77 | } 78 | } 79 | 80 | /// Tessellation control shader outputs. 81 | #[derive(Debug)] 82 | pub struct TessCtrlShaderOutputs { 83 | /// Outer tessellation levels. 84 | pub tess_level_outer: Var<[f32; 4]>, 85 | 86 | /// Inner tessellation levels. 87 | pub tess_level_inner: Var<[f32; 2]>, 88 | 89 | /// Array of per-vertex output variables. 90 | pub output: Var<[TessControlPerVertexOut]>, 91 | 92 | pub user: O, 93 | } 94 | 95 | impl TessCtrlShaderOutputs { 96 | pub(crate) const fn new(user: O) -> Self { 97 | let tess_level_outer = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessCtrl( 98 | TessCtrlBuiltIn::TessellationLevelOuter, 99 | ))); 100 | let tess_level_inner = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessCtrl( 101 | TessCtrlBuiltIn::TessellationLevelInner, 102 | ))); 103 | let output = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessCtrl( 104 | TessCtrlBuiltIn::Out, 105 | ))); 106 | 107 | Self { 108 | tess_level_outer, 109 | tess_level_inner, 110 | output, 111 | user, 112 | } 113 | } 114 | } 115 | 116 | impl Deref for TessCtrlShaderOutputs { 117 | type Target = O; 118 | 119 | fn deref(&self) -> &Self::Target { 120 | &self.user 121 | } 122 | } 123 | 124 | /// Output tessellation control shader environment. 125 | #[derive(Debug)] 126 | pub struct TessControlPerVertexOut; 127 | 128 | impl Expr { 129 | /// 4D position of the vertex. 130 | pub fn position(&self) -> Var> { 131 | let expr = ErasedExpr::Field { 132 | object: Box::new(self.erased.clone()), 133 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 134 | TessCtrlBuiltIn::Position, 135 | ))), 136 | }; 137 | 138 | Var(Expr::new(expr)) 139 | } 140 | 141 | /// Point size of the vertex. 142 | pub fn point_size(&self) -> Var { 143 | let expr = ErasedExpr::Field { 144 | object: Box::new(self.erased.clone()), 145 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 146 | TessCtrlBuiltIn::PointSize, 147 | ))), 148 | }; 149 | 150 | Var(Expr::new(expr)) 151 | } 152 | 153 | /// Clip distances to user-defined planes. 154 | pub fn clip_distance(&self) -> Var<[f32]> { 155 | let expr = ErasedExpr::Field { 156 | object: Box::new(self.erased.clone()), 157 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 158 | TessCtrlBuiltIn::ClipDistance, 159 | ))), 160 | }; 161 | 162 | Var(Expr::new(expr)) 163 | } 164 | 165 | /// Cull distances to user-defined planes. 166 | pub fn cull_distance(&self) -> Var<[f32]> { 167 | let expr = ErasedExpr::Field { 168 | object: Box::new(self.erased.clone()), 169 | field: Box::new(ErasedExpr::new_builtin(BuiltIn::TessCtrl( 170 | TessCtrlBuiltIn::CullDistance, 171 | ))), 172 | }; 173 | 174 | Var(Expr::new(expr)) 175 | } 176 | } 177 | 178 | /// Tessellation evalution shader outputs. 179 | #[derive(Debug)] 180 | pub struct TessEvalShaderOutputs { 181 | // outputs 182 | /// 4D position of the vertex. 183 | pub position: Var>, 184 | 185 | /// Point size of the vertex. 186 | pub point_size: Var, 187 | 188 | /// Clip distances to user-defined planes. 189 | pub clip_distance: Var<[f32]>, 190 | 191 | /// Cull distances to user-defined planes. 192 | pub cull_distance: Var<[f32]>, 193 | 194 | pub user: O, 195 | } 196 | 197 | impl TessEvalShaderOutputs { 198 | pub(crate) const fn new(user: O) -> Self { 199 | let position = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessEval( 200 | TessEvalBuiltIn::Position, 201 | ))); 202 | let point_size = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessEval( 203 | TessEvalBuiltIn::PointSize, 204 | ))); 205 | let clip_distance = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessEval( 206 | TessEvalBuiltIn::ClipDistance, 207 | ))); 208 | let cull_distance = Var::new(ScopedHandle::BuiltIn(BuiltIn::TessEval( 209 | TessEvalBuiltIn::ClipDistance, 210 | ))); 211 | 212 | Self { 213 | position, 214 | point_size, 215 | clip_distance, 216 | cull_distance, 217 | user, 218 | } 219 | } 220 | } 221 | 222 | impl Deref for TessEvalShaderOutputs { 223 | type Target = O; 224 | 225 | fn deref(&self) -> &Self::Target { 226 | &self.user 227 | } 228 | } 229 | 230 | /// Geometry shader outputs. 231 | #[derive(Debug)] 232 | pub struct GeometryShaderOutputs { 233 | /// Output 4D vertex position. 234 | pub position: Var>, 235 | 236 | /// Output vertex point size. 237 | pub point_size: Var, 238 | 239 | /// Output clip distances to user-defined planes. 240 | pub clip_distance: Var<[f32]>, 241 | 242 | /// Output cull distances to user-defined planes. 243 | pub cull_distance: Var<[f32]>, 244 | 245 | /// Primitive ID to write to in. 246 | pub primitive_id: Var, 247 | 248 | /// Layer to write to in. 249 | pub layer: Var, 250 | 251 | /// Viewport index to write to. 252 | pub viewport_index: Var, 253 | 254 | pub user: O, 255 | } 256 | 257 | impl GeometryShaderOutputs { 258 | pub(crate) const fn new(user: O) -> Self { 259 | let position = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 260 | GeometryBuiltIn::Position, 261 | ))); 262 | let point_size = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 263 | GeometryBuiltIn::PointSize, 264 | ))); 265 | let clip_distance = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 266 | GeometryBuiltIn::ClipDistance, 267 | ))); 268 | let cull_distance = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 269 | GeometryBuiltIn::CullDistance, 270 | ))); 271 | let primitive_id = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 272 | GeometryBuiltIn::PrimitiveID, 273 | ))); 274 | let layer = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 275 | GeometryBuiltIn::Layer, 276 | ))); 277 | let viewport_index = Var::new(ScopedHandle::BuiltIn(BuiltIn::Geometry( 278 | GeometryBuiltIn::ViewportIndex, 279 | ))); 280 | 281 | Self { 282 | position, 283 | point_size, 284 | clip_distance, 285 | cull_distance, 286 | primitive_id, 287 | layer, 288 | viewport_index, 289 | user, 290 | } 291 | } 292 | } 293 | 294 | impl Deref for GeometryShaderOutputs { 295 | type Target = O; 296 | 297 | fn deref(&self) -> &Self::Target { 298 | &self.user 299 | } 300 | } 301 | 302 | /// Fragment shader outputs. 303 | /// 304 | /// This type contains everything you have access to when writing a fragment shader. 305 | #[derive(Debug)] 306 | pub struct FragmentShaderOutputs { 307 | /// Depth of the fragment. 308 | pub frag_depth: Var, 309 | 310 | /// Sample mask of the fragment. 311 | pub sample_mask: Var<[i32]>, 312 | 313 | pub user: O, 314 | } 315 | 316 | impl FragmentShaderOutputs { 317 | pub(crate) const fn new(user: O) -> Self { 318 | let frag_depth = Var::new(ScopedHandle::BuiltIn(BuiltIn::Fragment( 319 | FragmentBuiltIn::FragDepth, 320 | ))); 321 | let sample_mask = Var::new(ScopedHandle::BuiltIn(BuiltIn::Fragment( 322 | FragmentBuiltIn::SampleMask, 323 | ))); 324 | 325 | Self { 326 | frag_depth, 327 | sample_mask, 328 | user, 329 | } 330 | } 331 | } 332 | 333 | impl Deref for FragmentShaderOutputs { 334 | type Target = O; 335 | 336 | fn deref(&self) -> &Self::Target { 337 | &self.user 338 | } 339 | } 340 | 341 | #[cfg(test)] 342 | mod test { 343 | use super::*; 344 | 345 | #[test] 346 | fn array_lookup() { 347 | let vertex = VertexShaderOutputs::new(()); 348 | let clip_dist_expr = vertex.clip_distance.at(Expr::from(1)); 349 | 350 | assert_eq!( 351 | clip_dist_expr.erased, 352 | ErasedExpr::ArrayLookup { 353 | object: Box::new(vertex.clip_distance.clone().erased), 354 | index: Box::new(ErasedExpr::LitInt(1)), 355 | } 356 | ); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /shades/src/scope.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | builtin::BuiltIn, 3 | erased::Erased, 4 | expr::{ErasedExpr, Expr}, 5 | fun::{ErasedReturn, Return}, 6 | types::{ToType, Type}, 7 | var::Var, 8 | }; 9 | use std::{ 10 | marker::PhantomData, 11 | ops::{Deref, DerefMut}, 12 | }; 13 | 14 | /// Lexical scope that must output an `R`. 15 | /// 16 | /// Scopes are the only way to add control flow expressions to shaders. [`Scope`] is the most general one, parent 17 | /// of all scopes. Depending on the kind of control flow, several kind of scopes are possible: 18 | /// 19 | /// - [`Scope`] is the most general one and every scopes share its features. 20 | /// - [`EscapeScope`] is a special kind of [`Scope`] that allows escaping from anywhere in the scope. 21 | /// - [`LoopScope`] is a special kind of [`EscapeScope`] that also allows to escape local looping expressions, 22 | /// such as `for` and `while` loops. 23 | /// 24 | /// A [`Scope`] allows to perform a bunch of actions: 25 | /// 26 | /// - Creating variable via [`Scope::var`]. Expressions of type [`Expr`] where [`T: ToType`](ToType) are bound in a 27 | /// [`Scope`] via [`Scope::var`] and a [`Var`] is returned, representing the bound variable. 28 | /// - Variable mutation via [`Scope::set`]. Any [`Var`] declared previously and still reachable in the current [`Scope`] 29 | /// can be mutated. 30 | /// - Introducing conditional statements with [`Scope::when`] and [`Scope::unless`]. 31 | /// - Introducing looping statements with [`Scope::loop_for`] and [`Scope::loop_while`]. 32 | #[derive(Debug)] 33 | pub struct Scope { 34 | pub(crate) erased: ErasedScope, 35 | _phantom: PhantomData, 36 | } 37 | 38 | impl Scope 39 | where 40 | Return: From, 41 | { 42 | /// Create a new [`Scope`] for which the ID is explicitly passed. 43 | /// 44 | /// The ID is unique in the scope hierarchy, but is not necessarily unique in the parent scope. What it means is that 45 | /// creating a scope `s` in a (parent) scope of ID `p` will give `s` the ID `p + 1`. So any scope created directly 46 | /// under the scope of ID `p` will get the `p + 1` ID. The reason for this is that variables go out of scope at the 47 | /// end of the scope they were created in, so it’s safe to reuse the same ID for sibling scopes, as they can’t share 48 | /// variables. 49 | pub fn new(id: u16) -> Self { 50 | Self { 51 | erased: ErasedScope::new(id), 52 | _phantom: PhantomData, 53 | } 54 | } 55 | 56 | /// Bind an expression to a variable in the current scope. 57 | /// 58 | /// `let v = s.var(e);` binds the `e` expression to `v` in the `s` [`Scope`], and `e` must have type [`Expr`] 59 | /// and `v` must be a [`Var`], with [`T: ToType`](ToType). 60 | /// 61 | /// # Return 62 | /// 63 | /// The resulting [`Var`] contains the representation of the binding in the EDSL and the actual binding is 64 | /// recorded in the current scope. 65 | pub fn var(&mut self, init_value: Expr) -> Var 66 | where 67 | T: ToType, 68 | { 69 | let n = self.erased.next_var; 70 | let handle = ScopedHandle::fun_var(self.erased.id, n); 71 | 72 | self.erased.next_var += 1; 73 | 74 | self.erased.instructions.push(ScopeInstr::VarDecl { 75 | ty: T::ty(), 76 | handle: handle.clone(), 77 | init_value: init_value.erased, 78 | }); 79 | 80 | Var::new(handle) 81 | } 82 | 83 | /// For looping statement — `for`. 84 | /// 85 | /// `s.loop_for(i, |i| /* cond */, |i| /* fold */, |i| /* body */ )` inserts a looping statement into the EDSL 86 | /// representing a typical “for” loop. `i` is an [`Expr`] satisfying [`T: ToType`](ToType) and is used as 87 | /// _initial_ value. 88 | /// 89 | /// In all the following closures, `i` refers to the initial value. 90 | /// 91 | /// The first `cond` closure must return an [`Expr`], representing the condition that is held until the loop 92 | /// exits. The second `fold` closure is a pure computation that must return an [`Expr`] and that will be evaluated 93 | /// at the end of each iteration before the next check on `cond`. The last and third `body` closure is the body of the 94 | /// loop. 95 | /// 96 | /// The behaviors of the first two closures is important to understand. Those are akin to _filtering_ and _folding_. 97 | /// The closure returning the [`Expr`] is given the [`Expr`] at each iteration and the second closure creates 98 | /// the new [`Expr`] for the next iteration. Normally, people are used to write this pattern as `i++`, for 99 | /// instance, but in the case of our EDSL, it is more akin go `i + 1`, and this value is affected to a local 100 | /// accumulator hidden from the user. 101 | /// 102 | /// The [`LoopScope`] argument to the `body` closure is a specialization of [`Scope`] that allows breaking out 103 | /// of loops. 104 | pub fn loop_for( 105 | &mut self, 106 | init_value: Expr, 107 | condition: impl FnOnce(&Expr) -> Expr, 108 | iter_fold: impl FnOnce(&Expr) -> Expr, 109 | body: impl FnOnce(&mut LoopScope, &Expr), 110 | ) where 111 | T: ToType, 112 | { 113 | let mut scope = LoopScope::new(self.deeper()); 114 | 115 | // bind the init value so that it’s available in all closures 116 | let init_var = scope.var(init_value); 117 | 118 | let condition = condition(&init_var); 119 | 120 | // generate the “post expr”, which is basically the free from of the third part of the for loop; people usually 121 | // set this to ++i, i++, etc., but in our case, the expression is to treat as a fold’s accumulator 122 | let post_expr = iter_fold(&init_var); 123 | 124 | body(&mut scope, &init_var); 125 | 126 | let scope = Scope::from(scope); 127 | self.erased.instructions.push(ScopeInstr::For { 128 | init_ty: T::ty(), 129 | init_handle: ScopedHandle::fun_var(scope.erased.id, 0), 130 | init_expr: init_var.to_expr().erased, 131 | condition: condition.erased, 132 | post_expr: post_expr.erased, 133 | scope: scope.erased, 134 | }); 135 | } 136 | 137 | /// While looping statement — `while`. 138 | /// 139 | /// `s.loop_while(cond, body)` inserts a looping statement into the EDSL representing a typical “while” loop. 140 | /// 141 | /// `cond` is an [`Expr`], representing the condition that is held until the loop exits. `body` is the content 142 | /// the loop will execute at each iteration. 143 | /// 144 | /// The [`LoopScope`] argument to the `body` closure is a specialization of [`Scope`] that allows breaking out 145 | /// of loops. 146 | pub fn loop_while( 147 | &mut self, 148 | condition: impl Into>, 149 | body: impl FnOnce(&mut LoopScope), 150 | ) { 151 | let mut scope = LoopScope::new(self.deeper()); 152 | body(&mut scope); 153 | 154 | self.erased.instructions.push(ScopeInstr::While { 155 | condition: condition.into().erased, 156 | scope: Scope::from(scope).erased, 157 | }); 158 | } 159 | 160 | /// Mutate a variable in the current scope. 161 | pub fn set(&mut self, var: Var, bin_op: Option, value: Expr) { 162 | self.erased.instructions.push(ScopeInstr::MutateVar { 163 | var: var.to_expr().erased, 164 | bin_op, 165 | expr: value.erased, 166 | }); 167 | } 168 | 169 | /// Early-return the current function with an expression. 170 | pub fn leave(&mut self, ret: impl Into) { 171 | self 172 | .erased 173 | .instructions 174 | .push(ScopeInstr::Return(Return::from(ret.into()).erased)); 175 | } 176 | } 177 | 178 | impl Scope<()> { 179 | /// Early-abort the current function. 180 | pub fn abort(&mut self) { 181 | self 182 | .erased 183 | .instructions 184 | .push(ScopeInstr::Return(ErasedReturn::Void)); 185 | } 186 | } 187 | 188 | impl Erased for Scope { 189 | type Erased = ErasedScope; 190 | 191 | fn to_erased(self) -> Self::Erased { 192 | self.erased 193 | } 194 | 195 | fn erased(&self) -> &Self::Erased { 196 | &self.erased 197 | } 198 | 199 | fn erased_mut(&mut self) -> &mut Self::Erased { 200 | &mut self.erased 201 | } 202 | } 203 | 204 | /// A special kind of [`Scope`] that can also break loops. 205 | #[derive(Debug)] 206 | pub struct LoopScope(Scope); 207 | 208 | impl From> for Scope { 209 | fn from(s: LoopScope) -> Self { 210 | s.0 211 | } 212 | } 213 | 214 | impl Deref for LoopScope { 215 | type Target = Scope; 216 | 217 | fn deref(&self) -> &Self::Target { 218 | &self.0 219 | } 220 | } 221 | 222 | impl DerefMut for LoopScope { 223 | fn deref_mut(&mut self) -> &mut Self::Target { 224 | &mut self.0 225 | } 226 | } 227 | 228 | impl LoopScope 229 | where 230 | Return: From, 231 | { 232 | fn new(s: Scope) -> Self { 233 | Self(s) 234 | } 235 | 236 | /// Break the current iteration of the nearest loop and continue to the next iteration. 237 | pub fn loop_continue(&mut self) { 238 | self.erased.instructions.push(ScopeInstr::Continue); 239 | } 240 | 241 | /// Break the nearest loop. 242 | pub fn loop_break(&mut self) { 243 | self.erased.instructions.push(ScopeInstr::Break); 244 | } 245 | } 246 | 247 | impl Erased for LoopScope { 248 | type Erased = ErasedScope; 249 | 250 | fn to_erased(self) -> Self::Erased { 251 | self.0.erased 252 | } 253 | 254 | fn erased(&self) -> &Self::Erased { 255 | &self.erased 256 | } 257 | 258 | fn erased_mut(&mut self) -> &mut Self::Erased { 259 | &mut self.erased 260 | } 261 | } 262 | 263 | #[derive(Clone, Debug, PartialEq)] 264 | pub struct ErasedScope { 265 | id: u16, 266 | pub(crate) instructions: Vec, 267 | next_var: u16, 268 | } 269 | 270 | impl ErasedScope { 271 | fn new(id: u16) -> Self { 272 | Self { 273 | id, 274 | instructions: Vec::new(), 275 | next_var: 0, 276 | } 277 | } 278 | } 279 | 280 | /// Go one level deeper in the scope. 281 | pub trait DeepScope { 282 | /// Create a new fresh scope under the current scope. 283 | fn deeper(&self) -> Self; 284 | } 285 | 286 | impl DeepScope for Scope 287 | where 288 | Return: From, 289 | { 290 | fn deeper(&self) -> Self { 291 | Scope::new(self.erased.id + 1) 292 | } 293 | } 294 | 295 | impl DeepScope for LoopScope 296 | where 297 | Return: From, 298 | { 299 | fn deeper(&self) -> Self { 300 | LoopScope(self.0.deeper()) 301 | } 302 | } 303 | 304 | /// Scopes allowing to enter conditional scopes. 305 | /// 306 | /// Conditional scopes allow to break out of a function by early-return / aborting the function. 307 | pub trait Conditional: Sized { 308 | /// Conditional statement — `if`. 309 | /// 310 | /// `s.when(cond, |s: &mut EscapeScope| { /* body */ })` inserts a conditional branch in the EDSL using the `cond` 311 | /// expression as truth and the passed closure as body to run when the represented condition is `true`. The 312 | /// [`EscapeScope`] provides you with the possibility to escape and leave the function earlier, either by returning 313 | /// an expression or by aborting the function, depending on the value of `R`: `Expr<_>` allows for early-returns and 314 | /// `()` allows for aborting. 315 | /// 316 | /// # Return 317 | /// 318 | /// A [`When`], authorizing the same escape rules with `R`. This object allows you to chain other conditional 319 | /// statements, commonly referred to as `else if` and `else` in common languages. 320 | /// 321 | /// Have a look at the documentation of [`When`] for further information. 322 | fn when<'a>( 323 | &'a mut self, 324 | condition: impl Into>, 325 | body: impl FnOnce(&mut Self), 326 | ) -> When<'a, Self>; 327 | 328 | /// Complement form of [`Scope::when`]. 329 | /// 330 | /// This method does the same thing as [`Scope::when`] but applies the [`Not::not`](std::ops::Not::not) operator on 331 | /// the condition first. 332 | fn unless<'a>( 333 | &'a mut self, 334 | condition: impl Into>, 335 | body: impl FnOnce(&mut Self), 336 | ) -> When<'a, Self> { 337 | self.when(!condition.into(), body) 338 | } 339 | } 340 | 341 | impl Conditional for S 342 | where 343 | S: DeepScope + Erased, 344 | { 345 | fn when<'a>( 346 | &'a mut self, 347 | condition: impl Into>, 348 | body: impl FnOnce(&mut Self), 349 | ) -> When<'a, Self> { 350 | let mut scope = self.deeper(); 351 | body(&mut scope); 352 | 353 | self.erased_mut().instructions.push(ScopeInstr::If { 354 | condition: condition.into().erased, 355 | scope: scope.erased().clone(), 356 | }); 357 | 358 | When { parent_scope: self } 359 | } 360 | } 361 | 362 | /// Conditional combinator. 363 | /// 364 | /// A [`When`] is returned from functions such as [`CanEscape::when`] or [`CanEscape::unless`] and allows to 365 | /// continue chaining conditional statements, encoding the concept of `else if` and `else` in more traditional languages. 366 | #[derive(Debug)] 367 | pub struct When<'a, S> { 368 | /// The scope from which this [`When`] expression comes from. 369 | /// 370 | /// This will be handy if we want to chain this when with others (corresponding to `else if` and `else`, for 371 | /// instance). 372 | parent_scope: &'a mut S, 373 | } 374 | 375 | impl When<'_, S> 376 | where 377 | S: DeepScope + Erased, 378 | { 379 | /// Add a conditional branch — `else if`. 380 | /// 381 | /// This method is often found chained after [`CanEscape::when`] and allows to add a new conditional if the previous 382 | /// conditional fails (i.e. `else if`). The behavior is the same as with [`CanEscape::when`]. 383 | /// 384 | /// # Return 385 | /// 386 | /// Another [`When`], allowing to add more conditional branches. 387 | pub fn or_else(self, condition: impl Into>, body: impl FnOnce(&mut S)) -> Self { 388 | let mut scope = self.parent_scope.deeper(); 389 | body(&mut scope); 390 | 391 | self 392 | .parent_scope 393 | .erased_mut() 394 | .instructions 395 | .push(ScopeInstr::ElseIf { 396 | condition: condition.into().erased, 397 | scope: scope.erased().clone(), 398 | }); 399 | 400 | self 401 | } 402 | 403 | /// Add a final catch-all conditional branch — `else`. 404 | /// 405 | /// This method is often found chained after [`CanEscape::when`] and allows to finish the chain of conditional 406 | /// branches if the previous conditional fails (i.e. `else`). The behavior is the same as with [`CanEscape::when`]. 407 | pub fn or(self, body: impl FnOnce(&mut S)) { 408 | let mut scope = self.parent_scope.deeper(); 409 | body(&mut scope); 410 | 411 | self 412 | .parent_scope 413 | .erased_mut() 414 | .instructions 415 | .push(ScopeInstr::Else { 416 | scope: scope.erased().clone(), 417 | }); 418 | } 419 | } 420 | 421 | /// Hierarchical and namespaced handle. 422 | /// 423 | /// Handles live in different namespaces: 424 | /// 425 | /// - The _built-in_ namespace gathers all built-ins. 426 | /// - The _global_ namespace gathers everything that can be declared at top-level of a shader stage — i.e. mainly 427 | /// constants for this namespace. 428 | /// - The _input_ namespace gathers inputs. 429 | /// - The _output_ namespace gathers outputs. 430 | /// - The _function argument_ namespace gives handles to function arguments, which exist only in a function body. 431 | /// - The _function variable_ namespace gives handles to variables defined in function bodies. This namespace is 432 | /// hierarchical: for each scope, a new namespace is created. The depth at which a namespace is located is referred to 433 | /// as its _subscope_. 434 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 435 | pub enum ScopedHandle { 436 | BuiltIn(BuiltIn), 437 | Global(u16), 438 | FunArg(u16), 439 | FunVar { subscope: u16, handle: u16 }, 440 | Input(u16), 441 | Output(u16), 442 | Env(String), 443 | } 444 | 445 | impl ScopedHandle { 446 | pub(crate) const fn builtin(b: BuiltIn) -> Self { 447 | Self::BuiltIn(b) 448 | } 449 | 450 | pub(crate) const fn global(handle: u16) -> Self { 451 | Self::Global(handle) 452 | } 453 | 454 | pub(crate) const fn fun_var(subscope: u16, handle: u16) -> Self { 455 | Self::FunVar { subscope, handle } 456 | } 457 | } 458 | 459 | #[derive(Clone, Debug, PartialEq)] 460 | pub enum ScopeInstr { 461 | VarDecl { 462 | ty: Type, 463 | handle: ScopedHandle, 464 | init_value: ErasedExpr, 465 | }, 466 | 467 | Return(ErasedReturn), 468 | 469 | Continue, 470 | 471 | Break, 472 | 473 | If { 474 | condition: ErasedExpr, 475 | scope: ErasedScope, 476 | }, 477 | 478 | ElseIf { 479 | condition: ErasedExpr, 480 | scope: ErasedScope, 481 | }, 482 | 483 | Else { 484 | scope: ErasedScope, 485 | }, 486 | 487 | For { 488 | init_ty: Type, 489 | init_handle: ScopedHandle, 490 | init_expr: ErasedExpr, 491 | condition: ErasedExpr, 492 | post_expr: ErasedExpr, 493 | scope: ErasedScope, 494 | }, 495 | 496 | While { 497 | condition: ErasedExpr, 498 | scope: ErasedScope, 499 | }, 500 | 501 | MutateVar { 502 | var: ErasedExpr, 503 | bin_op: Option, 504 | expr: ErasedExpr, 505 | }, 506 | } 507 | 508 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 509 | pub enum MutateBinOp { 510 | Add, 511 | Sub, 512 | Mul, 513 | Div, 514 | Rem, 515 | Xor, 516 | And, 517 | Or, 518 | Shl, 519 | Shr, 520 | } 521 | 522 | #[cfg(test)] 523 | mod test { 524 | use super::*; 525 | use crate::{ 526 | fun::ErasedFunHandle, 527 | types::{Dim, PrimType, V4}, 528 | vec4, 529 | }; 530 | 531 | #[test] 532 | fn when() { 533 | let mut s = Scope::>>::new(0); 534 | 535 | let x = s.var(Expr::from(1)); 536 | s.when(x.eq(Expr::from(2)), |s| { 537 | let y = s.var(vec4![1., 2., 3., 4.]); 538 | s.leave(y.to_expr()); 539 | }) 540 | .or_else(x.eq(Expr::from(0)), |s| s.leave(vec4![0., 0., 0., 0.])) 541 | .or(|_| ()); 542 | 543 | assert_eq!(s.erased.instructions.len(), 4); 544 | 545 | assert_eq!( 546 | s.erased.instructions[0], 547 | ScopeInstr::VarDecl { 548 | ty: Type { 549 | prim_ty: PrimType::Int(Dim::Scalar), 550 | array_dims: Vec::new(), 551 | }, 552 | handle: ScopedHandle::fun_var(0, 0), 553 | init_value: ErasedExpr::LitInt(1), 554 | } 555 | ); 556 | 557 | // if 558 | let mut scope = ErasedScope::new(1); 559 | scope.next_var = 1; 560 | scope.instructions.push(ScopeInstr::VarDecl { 561 | ty: Type { 562 | prim_ty: PrimType::Float(Dim::D4), 563 | array_dims: Vec::new(), 564 | }, 565 | handle: ScopedHandle::fun_var(1, 0), 566 | init_value: ErasedExpr::FunCall( 567 | ErasedFunHandle::Vec4, 568 | vec![ 569 | Expr::from(1.).erased, 570 | Expr::from(2.).erased, 571 | Expr::from(3.).erased, 572 | Expr::from(4.).erased, 573 | ], 574 | ), 575 | }); 576 | scope 577 | .instructions 578 | .push(ScopeInstr::Return(ErasedReturn::Expr( 579 | V4::::ty(), 580 | ErasedExpr::Var(ScopedHandle::fun_var(1, 0)), 581 | ))); 582 | 583 | assert_eq!( 584 | s.erased.instructions[1], 585 | ScopeInstr::If { 586 | condition: ErasedExpr::Eq( 587 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(0, 0))), 588 | Box::new(ErasedExpr::LitInt(2)), 589 | ), 590 | scope, 591 | } 592 | ); 593 | 594 | // else if 595 | let mut scope = ErasedScope::new(1); 596 | scope 597 | .instructions 598 | .push(ScopeInstr::Return(ErasedReturn::Expr( 599 | V4::::ty(), 600 | ErasedExpr::FunCall( 601 | ErasedFunHandle::Vec4, 602 | vec![ 603 | Expr::from(0.).erased, 604 | Expr::from(0.).erased, 605 | Expr::from(0.).erased, 606 | Expr::from(0.).erased, 607 | ], 608 | ), 609 | ))); 610 | 611 | assert_eq!( 612 | s.erased.instructions[2], 613 | ScopeInstr::ElseIf { 614 | condition: ErasedExpr::Eq( 615 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(0, 0))), 616 | Box::new(ErasedExpr::LitInt(0)), 617 | ), 618 | scope, 619 | } 620 | ); 621 | 622 | // else 623 | assert_eq!( 624 | s.erased.instructions[3], 625 | ScopeInstr::Else { 626 | scope: ErasedScope::new(1) 627 | } 628 | ); 629 | } 630 | 631 | #[test] 632 | fn for_loop() { 633 | let mut scope: Scope> = Scope::new(0); 634 | 635 | scope.loop_for( 636 | Expr::from(0), 637 | |a| a.clone().lt(Expr::from(10)), 638 | |a| a.clone() + Expr::from(1), 639 | |s, a| { 640 | s.leave(a.clone()); 641 | }, 642 | ); 643 | 644 | assert_eq!(scope.erased.instructions.len(), 1); 645 | 646 | let mut loop_scope = ErasedScope::new(1); 647 | loop_scope.next_var = 1; 648 | loop_scope.instructions.push(ScopeInstr::VarDecl { 649 | ty: Type { 650 | prim_ty: PrimType::Int(Dim::Scalar), 651 | array_dims: Vec::new(), 652 | }, 653 | handle: ScopedHandle::fun_var(1, 0), 654 | init_value: ErasedExpr::LitInt(0), 655 | }); 656 | loop_scope 657 | .instructions 658 | .push(ScopeInstr::Return(ErasedReturn::Expr( 659 | i32::ty(), 660 | ErasedExpr::Var(ScopedHandle::fun_var(1, 0)), 661 | ))); 662 | 663 | assert_eq!( 664 | scope.erased.instructions[0], 665 | ScopeInstr::For { 666 | init_ty: i32::ty(), 667 | init_handle: ScopedHandle::fun_var(1, 0), 668 | init_expr: ErasedExpr::Var(ScopedHandle::fun_var(1, 0)), 669 | condition: ErasedExpr::Lt( 670 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(1, 0))), 671 | Box::new(ErasedExpr::LitInt(10)), 672 | ), 673 | post_expr: ErasedExpr::Add( 674 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(1, 0))), 675 | Box::new(ErasedExpr::LitInt(1)), 676 | ), 677 | scope: loop_scope, 678 | } 679 | ); 680 | } 681 | 682 | #[test] 683 | fn while_loop() { 684 | let mut scope: Scope> = Scope::new(0); 685 | 686 | scope.loop_while(Expr::from(1).lt(Expr::from(2)), LoopScope::loop_continue); 687 | 688 | let mut loop_scope = ErasedScope::new(1); 689 | loop_scope.instructions.push(ScopeInstr::Continue); 690 | 691 | assert_eq!(scope.erased.instructions.len(), 1); 692 | assert_eq!( 693 | scope.erased.instructions[0], 694 | ScopeInstr::While { 695 | condition: ErasedExpr::Lt( 696 | Box::new(ErasedExpr::LitInt(1)), 697 | Box::new(ErasedExpr::LitInt(2)), 698 | ), 699 | scope: loop_scope, 700 | } 701 | ); 702 | } 703 | 704 | #[test] 705 | fn while_loop_if() { 706 | let mut scope: Scope> = Scope::new(0); 707 | 708 | scope.loop_while(Expr::from(1).lt(Expr::from(2)), |scope| { 709 | scope 710 | .when(Expr::from(1).lt(Expr::from(2)), |scope| scope.loop_break()) 711 | .or(|scope| scope.loop_break()); 712 | }); 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /shades/src/shader.rs: -------------------------------------------------------------------------------- 1 | use crate::{expr::ErasedExpr, fun::ErasedFun, types::Type}; 2 | 3 | /// Shader declaration. 4 | /// 5 | /// This contains everything that can be declared at top-level of a shader. 6 | #[derive(Debug)] 7 | pub(crate) enum ShaderDecl { 8 | /// The `main` function declaration. The [`ErasedFun`] is a function that returns nothing and has no argument. 9 | Main(ErasedFun), 10 | 11 | /// A function definition. 12 | /// 13 | /// The [`u16`] represents the _handle_ of the function, and is unique for each shader stage. The [`ErasedFun`] is 14 | /// the representation of the function definition. 15 | FunDef(u16, ErasedFun), 16 | 17 | /// A constant definition. 18 | /// 19 | /// The [`u16`] represents the _handle_ of the constant, and is unique for each shader stage. The [`Type`] is the 20 | /// the type of the constant expression. [`ErasedExpr`] is the representation of the constant. 21 | Const(u16, Type, ErasedExpr), 22 | } 23 | -------------------------------------------------------------------------------- /shades/src/stage.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{ 4 | env::Environment, 5 | expr::{ErasedExpr, Expr}, 6 | fun::{ErasedFunHandle, FunDef, FunHandle}, 7 | input::{ 8 | FragmentShaderInputs, GeometryShaderInputs, Inputs, TessCtrlShaderInputs, TessEvalShaderInputs, 9 | VertexShaderInputs, 10 | }, 11 | output::{ 12 | FragmentShaderOutputs, GeometryShaderOutputs, Outputs, TessCtrlShaderOutputs, 13 | TessEvalShaderOutputs, VertexShaderOutputs, 14 | }, 15 | scope::ScopedHandle, 16 | shader::ShaderDecl, 17 | types::ToType, 18 | }; 19 | 20 | /// A fully built shader stage as represented in Rust, obtained by adding the `main` function to a [`StageBuilder`]. 21 | #[derive(Debug)] 22 | pub struct Stage 23 | where 24 | S: ?Sized, 25 | { 26 | pub(crate) builder: ModBuilder, 27 | } 28 | 29 | /// Shader module. 30 | /// 31 | /// A shader module is defined by its type, which leads to the possible kinds ofinputs and outputs. 32 | /// 33 | /// Shader modules that define a `main` functions are usually called “shader stages”, and the ones that don’t are 34 | /// referred to as simple “modules.” 35 | pub trait ShaderModule { 36 | type Inputs; 37 | type Outputs; 38 | 39 | /// Create a new shader stage. 40 | /// 41 | /// This method creates a [`Stage`] that can be used only by the kind of flavour defined by the `S` type variable. 42 | /// 43 | /// # Return 44 | /// 45 | /// This method returns the fully built [`Stage`], which cannot be mutated anymore once it has been built, 46 | /// and can be passed to various [`writers`](crate::writer) to generate actual code for target shading languages. 47 | fn new_shader_module( 48 | f: impl FnOnce( 49 | ModBuilder, 50 | Self::Inputs, 51 | Self::Outputs, 52 | E::Env, 53 | ) -> Stage, 54 | ) -> Stage 55 | where 56 | E: Environment; 57 | } 58 | 59 | /// Vertex shader module. 60 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 61 | pub struct VS; 62 | 63 | impl ShaderModule for VS 64 | where 65 | I: Inputs, 66 | O: Outputs, 67 | { 68 | type Inputs = VertexShaderInputs; 69 | type Outputs = VertexShaderOutputs; 70 | 71 | fn new_shader_module( 72 | f: impl FnOnce( 73 | ModBuilder, 74 | Self::Inputs, 75 | Self::Outputs, 76 | E::Env, 77 | ) -> Stage, 78 | ) -> Stage 79 | where 80 | E: Environment, 81 | { 82 | f( 83 | ModBuilder::new(), 84 | VertexShaderInputs::new(I::input()), 85 | VertexShaderOutputs::new(O::output()), 86 | E::env(), 87 | ) 88 | } 89 | } 90 | 91 | /// Tessellation control shader module. 92 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 93 | pub struct TCS; 94 | 95 | impl ShaderModule for TCS 96 | where 97 | I: Inputs, 98 | O: Outputs, 99 | { 100 | type Inputs = TessCtrlShaderInputs; 101 | type Outputs = TessCtrlShaderOutputs; 102 | 103 | fn new_shader_module( 104 | f: impl FnOnce( 105 | ModBuilder, 106 | Self::Inputs, 107 | Self::Outputs, 108 | E::Env, 109 | ) -> Stage, 110 | ) -> Stage 111 | where 112 | E: Environment, 113 | { 114 | f( 115 | ModBuilder::new(), 116 | TessCtrlShaderInputs::new(I::input()), 117 | TessCtrlShaderOutputs::new(O::output()), 118 | E::env(), 119 | ) 120 | } 121 | } 122 | 123 | /// Tessellation evaluation shader module. 124 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 125 | pub struct TES; 126 | 127 | impl ShaderModule for TES 128 | where 129 | I: Inputs, 130 | O: Outputs, 131 | { 132 | type Inputs = TessEvalShaderInputs; 133 | type Outputs = TessEvalShaderOutputs; 134 | 135 | fn new_shader_module( 136 | f: impl FnOnce( 137 | ModBuilder, 138 | Self::Inputs, 139 | Self::Outputs, 140 | E::Env, 141 | ) -> Stage, 142 | ) -> Stage 143 | where 144 | E: Environment, 145 | { 146 | f( 147 | ModBuilder::new(), 148 | TessEvalShaderInputs::new(I::input()), 149 | TessEvalShaderOutputs::new(O::output()), 150 | E::env(), 151 | ) 152 | } 153 | } 154 | 155 | /// Geometry shader module. 156 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 157 | pub struct GS; 158 | 159 | impl ShaderModule for GS 160 | where 161 | I: Inputs, 162 | O: Outputs, 163 | { 164 | type Inputs = GeometryShaderInputs; 165 | type Outputs = GeometryShaderOutputs; 166 | 167 | fn new_shader_module( 168 | f: impl FnOnce( 169 | ModBuilder, 170 | Self::Inputs, 171 | Self::Outputs, 172 | E::Env, 173 | ) -> Stage, 174 | ) -> Stage 175 | where 176 | E: Environment, 177 | { 178 | f( 179 | ModBuilder::new(), 180 | GeometryShaderInputs::new(I::input()), 181 | GeometryShaderOutputs::new(O::output()), 182 | E::env(), 183 | ) 184 | } 185 | } 186 | 187 | /// Fragment shader module. 188 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 189 | pub struct FS; 190 | 191 | impl ShaderModule for FS 192 | where 193 | I: Inputs, 194 | O: Outputs, 195 | { 196 | type Inputs = FragmentShaderInputs; 197 | type Outputs = FragmentShaderOutputs; 198 | 199 | fn new_shader_module( 200 | f: impl FnOnce( 201 | ModBuilder, 202 | Self::Inputs, 203 | Self::Outputs, 204 | E::Env, 205 | ) -> Stage, 206 | ) -> Stage 207 | where 208 | E: Environment, 209 | { 210 | f( 211 | ModBuilder::new(), 212 | FragmentShaderInputs::new(I::input()), 213 | FragmentShaderOutputs::new(O::output()), 214 | E::env(), 215 | ) 216 | } 217 | } 218 | 219 | /// A shader module builder. 220 | /// 221 | /// This opaque type is the representation of a shader module in Rust. It contains constants, uniforms, inputs, outputs and 222 | /// functions declarations. 223 | /// 224 | /// It can also be sude to create shareable modules. 225 | #[derive(Debug)] 226 | pub struct ModBuilder 227 | where 228 | S: ?Sized, 229 | { 230 | pub(crate) decls: Vec, 231 | next_fun_handle: u16, 232 | next_global_handle: u16, 233 | _phantom: PhantomData<(*const S, I, O, E)>, 234 | } 235 | 236 | impl ModBuilder 237 | where 238 | S: ?Sized, 239 | I: Inputs, 240 | O: Outputs, 241 | E: Environment, 242 | { 243 | fn new() -> Self { 244 | Self { 245 | decls: Vec::new(), 246 | next_fun_handle: 0, 247 | next_global_handle: 0, 248 | _phantom: PhantomData, 249 | } 250 | } 251 | /// Create a new function in the shader and get its handle for future use. 252 | /// 253 | /// # Return 254 | /// 255 | /// This method returns a _function handle_, [`FunHandle`], where `R` is the return type and `A` the argument 256 | /// list of the function. This handle can be used in various positions in the EDSL but the most interesting place is 257 | /// in [`Expr`] and [`Var`], when calling the function to, respectively, combine it with other expressions or 258 | /// assign it to a variable. 259 | pub fn fun(&mut self, fundef: FunDef) -> FunHandle { 260 | let handle = self.next_fun_handle; 261 | self.next_fun_handle += 1; 262 | 263 | self.decls.push(ShaderDecl::FunDef(handle, fundef.erased)); 264 | 265 | FunHandle::new(ErasedFunHandle::UserDefined(handle as _)) 266 | } 267 | 268 | /// Declare a new constant, shared between all functions and constants that come next. 269 | /// 270 | /// The input argument is any object that can be transformed [`Into`] an [`Expr`]. At this level in the 271 | /// shader, pretty much nothing but literals and other constants are accepted here. 272 | /// 273 | /// # Return 274 | /// 275 | /// An [`Expr`] representing the constant passed as input. 276 | pub fn constant(&mut self, expr: Expr) -> Expr 277 | where 278 | T: ToType, 279 | { 280 | let handle = self.next_global_handle; 281 | self.next_global_handle += 1; 282 | 283 | self 284 | .decls 285 | .push(ShaderDecl::Const(handle, T::ty(), expr.erased)); 286 | 287 | Expr::new(ErasedExpr::Var(ScopedHandle::global(handle))) 288 | } 289 | } 290 | 291 | impl ModBuilder 292 | where 293 | S: ShaderModule, 294 | I: Inputs, 295 | O: Outputs, 296 | E: Environment, 297 | { 298 | /// Declare a new shader stage. 299 | pub fn new_stage( 300 | f: impl FnOnce(ModBuilder, S::Inputs, S::Outputs, E::Env) -> Stage, 301 | ) -> Stage { 302 | S::new_shader_module(f) 303 | } 304 | 305 | /// Declare the `main` function of the shader stage. 306 | /// 307 | /// This method is very similar to [`StageBuilder::fun`] in the sense it declares a function. However, it declares the special 308 | /// `main` entry-point of a shader stage, which doesn’t have any argument and returns nothing, and is the only 309 | /// way to finalize the building of a [`Stage`]. 310 | /// 311 | /// The input closure must take a single argument: a mutable reference on a `Scope<()>`, as the `main` function 312 | /// cannot return anything. 313 | /// 314 | /// # Return 315 | /// 316 | /// The fully built [`Stage`], which cannot be altered anymore. 317 | pub fn main_fun(mut self, fundef: FunDef<(), ()>) -> Stage { 318 | self.decls.push(ShaderDecl::Main(fundef.erased)); 319 | Stage { builder: self } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /shades/src/stdlib.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{ErasedExpr, Expr}, 3 | fun::ErasedFunHandle, 4 | types::{V2, V3, V4}, 5 | }; 6 | 7 | // standard library 8 | pub trait Trigonometry { 9 | fn radians(&self) -> Self; 10 | 11 | fn degrees(&self) -> Self; 12 | 13 | fn sin(&self) -> Self; 14 | 15 | fn cos(&self) -> Self; 16 | 17 | fn tan(&self) -> Self; 18 | 19 | fn asin(&self) -> Self; 20 | 21 | fn acos(&self) -> Self; 22 | 23 | fn atan(&self) -> Self; 24 | 25 | fn sinh(&self) -> Self; 26 | 27 | fn cosh(&self) -> Self; 28 | 29 | fn tanh(&self) -> Self; 30 | 31 | fn asinh(&self) -> Self; 32 | 33 | fn acosh(&self) -> Self; 34 | 35 | fn atanh(&self) -> Self; 36 | } 37 | 38 | macro_rules! impl_Trigonometry { 39 | ($t:ty) => { 40 | impl Trigonometry for Expr<$t> { 41 | fn radians(&self) -> Self { 42 | Expr::new(ErasedExpr::FunCall( 43 | ErasedFunHandle::Radians, 44 | vec![self.erased.clone()], 45 | )) 46 | } 47 | 48 | fn degrees(&self) -> Self { 49 | Expr::new(ErasedExpr::FunCall( 50 | ErasedFunHandle::Degrees, 51 | vec![self.erased.clone()], 52 | )) 53 | } 54 | 55 | fn sin(&self) -> Self { 56 | Expr::new(ErasedExpr::FunCall( 57 | ErasedFunHandle::Sin, 58 | vec![self.erased.clone()], 59 | )) 60 | } 61 | 62 | fn cos(&self) -> Self { 63 | Expr::new(ErasedExpr::FunCall( 64 | ErasedFunHandle::Cos, 65 | vec![self.erased.clone()], 66 | )) 67 | } 68 | 69 | fn tan(&self) -> Self { 70 | Expr::new(ErasedExpr::FunCall( 71 | ErasedFunHandle::Tan, 72 | vec![self.erased.clone()], 73 | )) 74 | } 75 | 76 | fn asin(&self) -> Self { 77 | Expr::new(ErasedExpr::FunCall( 78 | ErasedFunHandle::ASin, 79 | vec![self.erased.clone()], 80 | )) 81 | } 82 | 83 | fn acos(&self) -> Self { 84 | Expr::new(ErasedExpr::FunCall( 85 | ErasedFunHandle::ACos, 86 | vec![self.erased.clone()], 87 | )) 88 | } 89 | 90 | fn atan(&self) -> Self { 91 | Expr::new(ErasedExpr::FunCall( 92 | ErasedFunHandle::ATan, 93 | vec![self.erased.clone()], 94 | )) 95 | } 96 | 97 | fn sinh(&self) -> Self { 98 | Expr::new(ErasedExpr::FunCall( 99 | ErasedFunHandle::SinH, 100 | vec![self.erased.clone()], 101 | )) 102 | } 103 | 104 | fn cosh(&self) -> Self { 105 | Expr::new(ErasedExpr::FunCall( 106 | ErasedFunHandle::CosH, 107 | vec![self.erased.clone()], 108 | )) 109 | } 110 | 111 | fn tanh(&self) -> Self { 112 | Expr::new(ErasedExpr::FunCall( 113 | ErasedFunHandle::TanH, 114 | vec![self.erased.clone()], 115 | )) 116 | } 117 | 118 | fn asinh(&self) -> Self { 119 | Expr::new(ErasedExpr::FunCall( 120 | ErasedFunHandle::ASinH, 121 | vec![self.erased.clone()], 122 | )) 123 | } 124 | 125 | fn acosh(&self) -> Self { 126 | Expr::new(ErasedExpr::FunCall( 127 | ErasedFunHandle::ACosH, 128 | vec![self.erased.clone()], 129 | )) 130 | } 131 | 132 | fn atanh(&self) -> Self { 133 | Expr::new(ErasedExpr::FunCall( 134 | ErasedFunHandle::ATanH, 135 | vec![self.erased.clone()], 136 | )) 137 | } 138 | } 139 | }; 140 | } 141 | 142 | impl_Trigonometry!(f32); 143 | impl_Trigonometry!(V2); 144 | impl_Trigonometry!(V3); 145 | impl_Trigonometry!(V4); 146 | 147 | pub trait Exponential: Sized { 148 | fn pow(&self, p: impl Into) -> Self; 149 | 150 | fn exp(&self) -> Self; 151 | 152 | fn exp2(&self) -> Self; 153 | 154 | fn log(&self) -> Self; 155 | 156 | fn log2(&self) -> Self; 157 | 158 | fn sqrt(&self) -> Self; 159 | 160 | fn isqrt(&self) -> Self; 161 | } 162 | 163 | macro_rules! impl_Exponential { 164 | ($t:ty) => { 165 | impl Exponential for Expr<$t> { 166 | fn pow(&self, p: impl Into) -> Self { 167 | Expr::new(ErasedExpr::FunCall( 168 | ErasedFunHandle::Pow, 169 | vec![self.erased.clone(), p.into().erased], 170 | )) 171 | } 172 | 173 | fn exp(&self) -> Self { 174 | Expr::new(ErasedExpr::FunCall( 175 | ErasedFunHandle::Exp, 176 | vec![self.erased.clone()], 177 | )) 178 | } 179 | 180 | fn exp2(&self) -> Self { 181 | Expr::new(ErasedExpr::FunCall( 182 | ErasedFunHandle::Exp2, 183 | vec![self.erased.clone()], 184 | )) 185 | } 186 | 187 | fn log(&self) -> Self { 188 | Expr::new(ErasedExpr::FunCall( 189 | ErasedFunHandle::Log, 190 | vec![self.erased.clone()], 191 | )) 192 | } 193 | 194 | fn log2(&self) -> Self { 195 | Expr::new(ErasedExpr::FunCall( 196 | ErasedFunHandle::Log2, 197 | vec![self.erased.clone()], 198 | )) 199 | } 200 | 201 | fn sqrt(&self) -> Self { 202 | Expr::new(ErasedExpr::FunCall( 203 | ErasedFunHandle::Sqrt, 204 | vec![self.erased.clone()], 205 | )) 206 | } 207 | 208 | fn isqrt(&self) -> Self { 209 | Expr::new(ErasedExpr::FunCall( 210 | ErasedFunHandle::InverseSqrt, 211 | vec![self.erased.clone()], 212 | )) 213 | } 214 | } 215 | }; 216 | } 217 | 218 | impl_Exponential!(f32); 219 | impl_Exponential!(V2); 220 | impl_Exponential!(V3); 221 | impl_Exponential!(V4); 222 | 223 | pub trait Relative { 224 | fn abs(&self) -> Self; 225 | 226 | fn sign(&self) -> Self; 227 | } 228 | 229 | macro_rules! impl_Relative { 230 | ($t:ty) => { 231 | impl Relative for Expr<$t> { 232 | fn abs(&self) -> Self { 233 | Expr::new(ErasedExpr::FunCall( 234 | ErasedFunHandle::Abs, 235 | vec![self.erased.clone()], 236 | )) 237 | } 238 | 239 | fn sign(&self) -> Self { 240 | Expr::new(ErasedExpr::FunCall( 241 | ErasedFunHandle::Sign, 242 | vec![self.erased.clone()], 243 | )) 244 | } 245 | } 246 | }; 247 | } 248 | 249 | impl_Relative!(i32); 250 | impl_Relative!(V2); 251 | impl_Relative!(V3); 252 | impl_Relative!(V4); 253 | impl_Relative!(f32); 254 | impl_Relative!(V2); 255 | impl_Relative!(V3); 256 | impl_Relative!(V4); 257 | 258 | pub trait Floating { 259 | fn floor(&self) -> Self; 260 | 261 | fn trunc(&self) -> Self; 262 | 263 | fn round(&self) -> Self; 264 | 265 | fn ceil(&self) -> Self; 266 | 267 | fn fract(&self) -> Self; 268 | } 269 | 270 | macro_rules! impl_Floating { 271 | ($t:ty) => { 272 | impl Floating for Expr<$t> { 273 | fn floor(&self) -> Self { 274 | Expr::new(ErasedExpr::FunCall( 275 | ErasedFunHandle::Floor, 276 | vec![self.erased.clone()], 277 | )) 278 | } 279 | 280 | fn trunc(&self) -> Self { 281 | Expr::new(ErasedExpr::FunCall( 282 | ErasedFunHandle::Trunc, 283 | vec![self.erased.clone()], 284 | )) 285 | } 286 | 287 | fn round(&self) -> Self { 288 | Expr::new(ErasedExpr::FunCall( 289 | ErasedFunHandle::Round, 290 | vec![self.erased.clone()], 291 | )) 292 | } 293 | 294 | fn ceil(&self) -> Self { 295 | Expr::new(ErasedExpr::FunCall( 296 | ErasedFunHandle::Ceil, 297 | vec![self.erased.clone()], 298 | )) 299 | } 300 | 301 | fn fract(&self) -> Self { 302 | Expr::new(ErasedExpr::FunCall( 303 | ErasedFunHandle::Fract, 304 | vec![self.erased.clone()], 305 | )) 306 | } 307 | } 308 | }; 309 | } 310 | 311 | impl_Floating!(f32); 312 | impl_Floating!(V2); 313 | impl_Floating!(V3); 314 | impl_Floating!(V4); 315 | 316 | pub trait Bounded: Sized { 317 | fn min(self, rhs: Self) -> Self; 318 | 319 | fn max(self, rhs: Self) -> Self; 320 | 321 | fn clamp(self, min_value: Self, max_value: Self) -> Self; 322 | } 323 | 324 | macro_rules! impl_Bounded { 325 | ($t:ty) => { 326 | impl Bounded for Expr<$t> { 327 | fn min(self, rhs: Self) -> Self { 328 | Expr::new(ErasedExpr::FunCall( 329 | ErasedFunHandle::Min, 330 | vec![self.erased, rhs.erased], 331 | )) 332 | } 333 | 334 | fn max(self, rhs: Self) -> Self { 335 | Expr::new(ErasedExpr::FunCall( 336 | ErasedFunHandle::Max, 337 | vec![self.erased, rhs.erased], 338 | )) 339 | } 340 | 341 | fn clamp(self, min_value: Self, max_value: Self) -> Self { 342 | Expr::new(ErasedExpr::FunCall( 343 | ErasedFunHandle::Clamp, 344 | vec![self.erased, min_value.erased, max_value.erased], 345 | )) 346 | } 347 | } 348 | }; 349 | } 350 | 351 | impl_Bounded!(i32); 352 | impl_Bounded!(V2); 353 | impl_Bounded!(V3); 354 | impl_Bounded!(V4); 355 | 356 | impl_Bounded!(u32); 357 | impl_Bounded!(V2); 358 | impl_Bounded!(V3); 359 | impl_Bounded!(V4); 360 | 361 | impl_Bounded!(f32); 362 | impl_Bounded!(V2); 363 | impl_Bounded!(V3); 364 | impl_Bounded!(V4); 365 | 366 | impl_Bounded!(bool); 367 | impl_Bounded!(V2); 368 | impl_Bounded!(V3); 369 | impl_Bounded!(V4); 370 | 371 | pub trait Mix: Sized { 372 | fn mix(self, y: Self, a: RHS) -> Self; 373 | 374 | fn step(self, edge: RHS) -> Self; 375 | 376 | fn smooth_step(self, edge_a: RHS, edge_b: RHS) -> Self; 377 | } 378 | 379 | macro_rules! impl_Mix { 380 | ($t:ty, $q:ty) => { 381 | impl Mix> for Expr<$t> { 382 | fn mix(self, y: Self, a: Expr<$q>) -> Self { 383 | Expr::new(ErasedExpr::FunCall( 384 | ErasedFunHandle::Mix, 385 | vec![self.erased, y.erased, a.erased], 386 | )) 387 | } 388 | 389 | fn step(self, edge: Expr<$q>) -> Self { 390 | Expr::new(ErasedExpr::FunCall( 391 | ErasedFunHandle::Step, 392 | vec![self.erased, edge.erased], 393 | )) 394 | } 395 | 396 | fn smooth_step(self, edge_a: Expr<$q>, edge_b: Expr<$q>) -> Self { 397 | Expr::new(ErasedExpr::FunCall( 398 | ErasedFunHandle::SmoothStep, 399 | vec![self.erased, edge_a.erased, edge_b.erased], 400 | )) 401 | } 402 | } 403 | }; 404 | } 405 | 406 | impl_Mix!(f32, f32); 407 | impl_Mix!(V2, f32); 408 | impl_Mix!(V2, V2); 409 | 410 | impl_Mix!(V3, f32); 411 | impl_Mix!(V3, V3); 412 | 413 | impl_Mix!(V4, f32); 414 | impl_Mix!(V4, V4); 415 | 416 | pub trait FloatingExt { 417 | type BoolExpr; 418 | 419 | fn is_nan(self) -> Self::BoolExpr; 420 | 421 | fn is_inf(self) -> Self::BoolExpr; 422 | } 423 | 424 | macro_rules! impl_FloatingExt { 425 | ($t:ty, $bool_expr:ty) => { 426 | impl FloatingExt for Expr<$t> { 427 | type BoolExpr = Expr<$bool_expr>; 428 | 429 | fn is_nan(self) -> Self::BoolExpr { 430 | Expr::new(ErasedExpr::FunCall( 431 | ErasedFunHandle::IsNan, 432 | vec![self.erased], 433 | )) 434 | } 435 | 436 | fn is_inf(self) -> Self::BoolExpr { 437 | Expr::new(ErasedExpr::FunCall( 438 | ErasedFunHandle::IsInf, 439 | vec![self.erased], 440 | )) 441 | } 442 | } 443 | }; 444 | } 445 | 446 | impl_FloatingExt!(f32, bool); 447 | impl_FloatingExt!(V2, V2); 448 | impl_FloatingExt!(V3, V3); 449 | impl_FloatingExt!(V4, V4); 450 | 451 | pub trait Geometry: Sized { 452 | type LengthExpr; 453 | 454 | fn length(self) -> Self::LengthExpr; 455 | 456 | fn distance(self, other: Self) -> Self::LengthExpr; 457 | 458 | fn dot(self, other: Self) -> Self::LengthExpr; 459 | 460 | fn cross(self, other: Self) -> Self; 461 | 462 | fn normalize(self) -> Self; 463 | 464 | fn face_forward(self, normal: Self, reference: Self) -> Self; 465 | 466 | fn reflect(self, normal: Self) -> Self; 467 | 468 | fn refract(self, normal: Self, eta: Expr) -> Self; 469 | } 470 | 471 | macro_rules! impl_Geometry { 472 | ($t:ty, $l:ty) => { 473 | impl Geometry for Expr<$t> { 474 | type LengthExpr = Expr<$l>; 475 | 476 | fn length(self) -> Self::LengthExpr { 477 | Expr::new(ErasedExpr::FunCall( 478 | ErasedFunHandle::Length, 479 | vec![self.erased], 480 | )) 481 | } 482 | 483 | fn distance(self, other: Self) -> Self::LengthExpr { 484 | Expr::new(ErasedExpr::FunCall( 485 | ErasedFunHandle::Distance, 486 | vec![self.erased, other.erased], 487 | )) 488 | } 489 | 490 | fn dot(self, other: Self) -> Self::LengthExpr { 491 | Expr::new(ErasedExpr::FunCall( 492 | ErasedFunHandle::Dot, 493 | vec![self.erased, other.erased], 494 | )) 495 | } 496 | 497 | fn cross(self, other: Self) -> Self { 498 | Expr::new(ErasedExpr::FunCall( 499 | ErasedFunHandle::Cross, 500 | vec![self.erased, other.erased], 501 | )) 502 | } 503 | 504 | fn normalize(self) -> Self { 505 | Expr::new(ErasedExpr::FunCall( 506 | ErasedFunHandle::Normalize, 507 | vec![self.erased], 508 | )) 509 | } 510 | 511 | fn face_forward(self, normal: Self, reference: Self) -> Self { 512 | // note: this function call is super weird as the normal and incident (i.e. self) arguments are swapped 513 | Expr::new(ErasedExpr::FunCall( 514 | ErasedFunHandle::FaceForward, 515 | vec![normal.erased, self.erased, reference.erased], 516 | )) 517 | } 518 | 519 | fn reflect(self, normal: Self) -> Self { 520 | Expr::new(ErasedExpr::FunCall( 521 | ErasedFunHandle::Reflect, 522 | vec![self.erased, normal.erased], 523 | )) 524 | } 525 | 526 | fn refract(self, normal: Self, eta: Expr) -> Self { 527 | Expr::new(ErasedExpr::FunCall( 528 | ErasedFunHandle::Refract, 529 | vec![self.erased, normal.erased, eta.erased], 530 | )) 531 | } 532 | } 533 | }; 534 | } 535 | 536 | impl_Geometry!(V2, f32); 537 | impl_Geometry!(V3, f32); 538 | impl_Geometry!(V4, f32); 539 | -------------------------------------------------------------------------------- /shades/src/swizzle.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{ErasedExpr, Expr}, 3 | types::{V2, V3, V4}, 4 | }; 5 | 6 | /// Select a channel to extract from into a swizzled expession. 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub enum SwizzleSelector { 9 | /// Select the `.x` (or `.r`) channel. 10 | X, 11 | 12 | /// Select the `.y` (or `.g`) channel. 13 | Y, 14 | 15 | /// Select the `.z` (or `.b`) channel. 16 | Z, 17 | 18 | /// Select the `.w` (or `.a`) channel. 19 | W, 20 | } 21 | 22 | /// Swizzle channel selector. 23 | /// 24 | /// This type gives the dimension of the target expression (output) and dimension of the source expression (input). The 25 | /// [`SwizzleSelector`] also to select a specific channel in the input expression. 26 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 27 | pub enum Swizzle { 28 | /// Create a one-channel expression. 29 | D1(SwizzleSelector), 30 | 31 | /// Create a two-channel expression. 32 | D2(SwizzleSelector, SwizzleSelector), 33 | 34 | /// Create a three-channel expression. 35 | D3(SwizzleSelector, SwizzleSelector, SwizzleSelector), 36 | 37 | /// Create a four-channel expression. 38 | D4( 39 | SwizzleSelector, 40 | SwizzleSelector, 41 | SwizzleSelector, 42 | SwizzleSelector, 43 | ), 44 | } 45 | 46 | /// Interface to implement to swizzle an expression. 47 | /// 48 | /// If you plan to use your implementor with the [`sw!`](sw) macro, `S` must be one of the following types: 49 | /// 50 | /// - [`SwizzleSelector`]: to implement `sw!(.x)`. 51 | /// - [[`SwizzleSelector`]; 2]: to implement `sw!(.xx)`. 52 | /// - [[`SwizzleSelector`]; 3]: to implement `sw!(.xxx)`. 53 | /// - [[`SwizzleSelector`]; 4]: to implement `sw!(.xxxx)`. 54 | pub trait Swizzlable { 55 | type Output; 56 | 57 | fn swizzle(&self, sw: S) -> Self::Output; 58 | } 59 | 60 | // 2D 61 | impl Swizzlable for Expr> { 62 | type Output = Expr; 63 | 64 | fn swizzle(&self, x: SwizzleSelector) -> Self::Output { 65 | Expr::new(ErasedExpr::Swizzle( 66 | Box::new(self.erased.clone()), 67 | Swizzle::D1(x), 68 | )) 69 | } 70 | } 71 | 72 | impl Swizzlable<[SwizzleSelector; 2]> for Expr> { 73 | type Output = Self; 74 | 75 | fn swizzle(&self, [x, y]: [SwizzleSelector; 2]) -> Self::Output { 76 | Expr::new(ErasedExpr::Swizzle( 77 | Box::new(self.erased.clone()), 78 | Swizzle::D2(x, y), 79 | )) 80 | } 81 | } 82 | 83 | // 3D 84 | impl Swizzlable for Expr> { 85 | type Output = Expr; 86 | 87 | fn swizzle(&self, x: SwizzleSelector) -> Self::Output { 88 | Expr::new(ErasedExpr::Swizzle( 89 | Box::new(self.erased.clone()), 90 | Swizzle::D1(x), 91 | )) 92 | } 93 | } 94 | 95 | impl Swizzlable<[SwizzleSelector; 2]> for Expr> { 96 | type Output = Expr>; 97 | 98 | fn swizzle(&self, [x, y]: [SwizzleSelector; 2]) -> Self::Output { 99 | Expr::new(ErasedExpr::Swizzle( 100 | Box::new(self.erased.clone()), 101 | Swizzle::D2(x, y), 102 | )) 103 | } 104 | } 105 | 106 | impl Swizzlable<[SwizzleSelector; 3]> for Expr> { 107 | type Output = Self; 108 | 109 | fn swizzle(&self, [x, y, z]: [SwizzleSelector; 3]) -> Self::Output { 110 | Expr::new(ErasedExpr::Swizzle( 111 | Box::new(self.erased.clone()), 112 | Swizzle::D3(x, y, z), 113 | )) 114 | } 115 | } 116 | 117 | // 4D 118 | impl Swizzlable for Expr> { 119 | type Output = Expr; 120 | 121 | fn swizzle(&self, x: SwizzleSelector) -> Self::Output { 122 | Expr::new(ErasedExpr::Swizzle( 123 | Box::new(self.erased.clone()), 124 | Swizzle::D1(x), 125 | )) 126 | } 127 | } 128 | 129 | impl Swizzlable<[SwizzleSelector; 2]> for Expr> { 130 | type Output = Expr>; 131 | 132 | fn swizzle(&self, [x, y]: [SwizzleSelector; 2]) -> Self::Output { 133 | Expr::new(ErasedExpr::Swizzle( 134 | Box::new(self.erased.clone()), 135 | Swizzle::D2(x, y), 136 | )) 137 | } 138 | } 139 | 140 | impl Swizzlable<[SwizzleSelector; 3]> for Expr> { 141 | type Output = Expr>; 142 | 143 | fn swizzle(&self, [x, y, z]: [SwizzleSelector; 3]) -> Self::Output { 144 | Expr::new(ErasedExpr::Swizzle( 145 | Box::new(self.erased.clone()), 146 | Swizzle::D3(x, y, z), 147 | )) 148 | } 149 | } 150 | 151 | impl Swizzlable<[SwizzleSelector; 4]> for Expr> { 152 | type Output = Self; 153 | 154 | fn swizzle(&self, [x, y, z, w]: [SwizzleSelector; 4]) -> Self::Output { 155 | Expr::new(ErasedExpr::Swizzle( 156 | Box::new(self.erased.clone()), 157 | Swizzle::D4(x, y, z, w), 158 | )) 159 | } 160 | } 161 | 162 | /// Expressions having a `x` or `r` coordinate. 163 | /// 164 | /// Akin to swizzling with `.x` or `.r`, but easier. 165 | pub trait HasX { 166 | type Output; 167 | 168 | fn x(&self) -> Self::Output; 169 | fn r(&self) -> Self::Output { 170 | self.x() 171 | } 172 | } 173 | 174 | /// Expressions having a `y` or `g` coordinate. 175 | /// 176 | /// Akin to swizzling with `.y` or `.g`, but easier. 177 | pub trait HasY { 178 | type Output; 179 | 180 | fn y(&self) -> Self::Output; 181 | fn g(&self) -> Self::Output { 182 | self.y() 183 | } 184 | } 185 | 186 | /// Expressions having a `z` or `b` coordinate. 187 | /// 188 | /// Akin to swizzling with `.z` or `.b`, but easier. 189 | pub trait HasZ { 190 | type Output; 191 | 192 | fn z(&self) -> Self::Output; 193 | fn b(&self) -> Self::Output { 194 | self.z() 195 | } 196 | } 197 | 198 | /// Expressions having a `w` or `a` coordinate. 199 | /// 200 | /// Akin to swizzling with `.w` or `.a`, but easier. 201 | pub trait HasW { 202 | type Output; 203 | 204 | fn w(&self) -> Self::Output; 205 | fn a(&self) -> Self::Output { 206 | self.w() 207 | } 208 | } 209 | 210 | macro_rules! impl_has_k { 211 | ($trait:ident, $name:ident, $selector:ident, $t:ident) => { 212 | impl $trait for Expr<$t> { 213 | type Output = Expr; 214 | 215 | fn $name(&self) -> Self::Output { 216 | self.swizzle(SwizzleSelector::$selector) 217 | } 218 | } 219 | }; 220 | } 221 | 222 | impl_has_k!(HasX, x, X, V2); 223 | impl_has_k!(HasX, x, X, V3); 224 | impl_has_k!(HasX, x, X, V4); 225 | 226 | impl_has_k!(HasY, y, Y, V2); 227 | impl_has_k!(HasY, y, Y, V3); 228 | impl_has_k!(HasY, y, Y, V4); 229 | 230 | impl_has_k!(HasZ, z, Z, V3); 231 | impl_has_k!(HasZ, z, Z, V4); 232 | 233 | impl_has_k!(HasW, w, W, V4); 234 | 235 | /// Swizzle macro. 236 | /// 237 | /// This macro allows to swizzle expressions to yield expressions reorganizing the vector attributes. For instance, 238 | /// `sw!(color, .rgbr)` will take a 4D color and will output a 4D color for which the alpha channel is overridden with 239 | /// the red channel. 240 | /// 241 | /// The current syntax allows to extract and construct from a lot of types. Have a look at [`Swizzlable`] for a 242 | /// comprehensive list of what you can do. 243 | #[macro_export] 244 | macro_rules! sw { 245 | ($e:expr, . $a:tt) => { 246 | $e.swizzle($crate::sw_extract!($a)) 247 | }; 248 | 249 | ($e:expr, . $a:tt . $b:tt) => { 250 | $e.swizzle([$crate::sw_extract!($a), $crate::sw_extract!($b)]) 251 | }; 252 | 253 | ($e:expr, . $a:tt . $b:tt . $c:tt) => { 254 | $e.swizzle([ 255 | $crate::sw_extract!($a), 256 | $crate::sw_extract!($b), 257 | $crate::sw_extract!($c), 258 | ]) 259 | }; 260 | 261 | ($e:expr, . $a:tt . $b:tt . $c:tt . $d:tt) => { 262 | $e.swizzle([ 263 | $crate::sw_extract!($a), 264 | $crate::sw_extract!($b), 265 | $crate::sw_extract!($c), 266 | $crate::sw_extract!($d), 267 | ]) 268 | }; 269 | } 270 | 271 | #[doc(hidden)] 272 | #[macro_export] 273 | macro_rules! sw_extract { 274 | (x) => { 275 | $crate::swizzle::SwizzleSelector::X 276 | }; 277 | 278 | (r) => { 279 | $crate::swizzle::SwizzleSelector::X 280 | }; 281 | 282 | (y) => { 283 | $crate::swizzle::SwizzleSelector::Y 284 | }; 285 | 286 | (g) => { 287 | $crate::swizzle::SwizzleSelector::Y 288 | }; 289 | 290 | (z) => { 291 | $crate::swizzle::SwizzleSelector::Z 292 | }; 293 | 294 | (b) => { 295 | $crate::swizzle::SwizzleSelector::Z 296 | }; 297 | 298 | (w) => { 299 | $crate::swizzle::SwizzleSelector::W 300 | }; 301 | 302 | (a) => { 303 | $crate::swizzle::SwizzleSelector::W 304 | }; 305 | } 306 | 307 | #[cfg(test)] 308 | mod test { 309 | use crate::{ 310 | scope::{Scope, ScopedHandle}, 311 | vec2, vec4, 312 | }; 313 | 314 | use super::*; 315 | 316 | #[test] 317 | fn swizzling() { 318 | let mut scope = Scope::<()>::new(0); 319 | let foo = scope.var(vec2![1, 2]); 320 | let foo_xy: Expr> = sw!(foo, .x.y); 321 | let foo_xx: Expr> = sw!(foo, .x.x); 322 | 323 | assert_eq!( 324 | foo_xy.erased, 325 | ErasedExpr::Swizzle( 326 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(0, 0))), 327 | Swizzle::D2(SwizzleSelector::X, SwizzleSelector::Y), 328 | ) 329 | ); 330 | 331 | assert_eq!( 332 | foo_xx.erased, 333 | ErasedExpr::Swizzle( 334 | Box::new(ErasedExpr::Var(ScopedHandle::fun_var(0, 0))), 335 | Swizzle::D2(SwizzleSelector::X, SwizzleSelector::X), 336 | ) 337 | ); 338 | } 339 | 340 | #[test] 341 | fn has_x_y_z_w() { 342 | let xyzw: Expr> = vec4![1, 2, 3, 4]; 343 | let x: Expr = sw!(xyzw, .x); 344 | let y: Expr = sw!(xyzw, .y); 345 | let z: Expr = sw!(xyzw, .z); 346 | let w: Expr = sw!(xyzw, .w); 347 | 348 | assert_eq!(xyzw.x().erased, x.erased); 349 | assert_eq!(xyzw.y().erased, y.erased); 350 | assert_eq!(xyzw.z().erased, z.erased); 351 | assert_eq!(xyzw.w().erased, w.erased); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /shades/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{ErasedExpr, Expr}, 3 | fun::ErasedFunHandle, 4 | }; 5 | use std::iter::once; 6 | 7 | macro_rules! make_vn { 8 | ($t:ident, $dim:expr) => { 9 | /// Scalar vectors. 10 | /// 11 | /// Scalar vectors come into three flavors, based on the dimension used: 12 | /// 13 | /// - Two dimensions (2D): [`V2`]. 14 | /// - Three dimensions (3D): [`V3`]. 15 | /// - Four dimensions (4D): [`V4`]. 16 | /// 17 | /// Each type implements the [`From`] trait for sized array. For instance, if you want to make a `V3` from 18 | /// constants / literals, you can simply use the implementor `From<[f32; 3]> for V3`. 19 | /// 20 | /// A builder macro version exists for each flavor: 21 | /// 22 | /// - [`vec2`]: build [`V2`]. 23 | /// - [`vec3`]: build [`V3`]. 24 | /// - [`vec4`]: build [`V4`]. 25 | /// 26 | /// Those three macros can also be used with literals. 27 | #[derive(Clone, Copy, Debug, PartialEq)] 28 | pub struct $t(pub [T; $dim]); 29 | 30 | impl From<[T; $dim]> for $t { 31 | fn from(a: [T; $dim]) -> Self { 32 | Self(a) 33 | } 34 | } 35 | }; 36 | } 37 | 38 | make_vn!(V2, 2); 39 | make_vn!(V3, 3); 40 | make_vn!(V4, 4); 41 | 42 | /// Matrix wrapper. 43 | /// 44 | /// This type represents a matrix of a given dimension, deduced from the wrapped type. 45 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 46 | pub struct Matrix(pub T); 47 | 48 | impl From<[[T; N]; M]> for Matrix<[[T; N]; M]> { 49 | fn from(a: [[T; N]; M]) -> Self { 50 | Matrix(a) 51 | } 52 | } 53 | 54 | macro_rules! make_mat_ty { 55 | ($t:ident, $lit:ident, $m:expr, $n:expr, $mdim:ident) => { 56 | pub type $t = Matrix<[[f32; $n]; $m]>; 57 | 58 | impl ToPrimType for Matrix<[[f32; $n]; $m]> { 59 | const PRIM_TYPE: PrimType = PrimType::Matrix(MatrixDim::$mdim); 60 | } 61 | 62 | impl From> for Expr> { 63 | fn from(matrix: Matrix<[[f32; $n]; $m]>) -> Self { 64 | Self::new(ErasedExpr::$lit(matrix)) 65 | } 66 | } 67 | }; 68 | } 69 | 70 | make_mat_ty!(M22, LitM22, 2, 2, D22); 71 | // make_mat_ty!(M23, LitM23, 2, 3, D23); 72 | // make_mat_ty!(M24, LitM24, 2, 4, D24); 73 | // make_mat_ty!(M32, LitM32, 3, 2, D32); 74 | make_mat_ty!(M33, LitM33, 3, 3, D33); 75 | // make_mat_ty!(M34, LitM34, 3, 4, D34); 76 | // make_mat_ty!(M42, LitM42, 4, 2, D42); 77 | // make_mat_ty!(M43, LitM43, 4, 3, D43); 78 | make_mat_ty!(M44, LitM44, 4, 4, D44); 79 | 80 | /// Matrix dimension. 81 | /// 82 | /// Matrices can have several dimensions. Most of the time, you will be interested in squared dimensions, e.g. 2×2, 3×3 83 | /// and 4×4. However, other dimensions exist. 84 | /// 85 | /// > Note: matrices are expressed in column-major. 86 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 87 | pub enum MatrixDim { 88 | /// Squared 2 dimension. 89 | D22, 90 | /// 2×3 dimension. 91 | D23, 92 | /// 2×4 dimension. 93 | D24, 94 | /// 3×2 dimension. 95 | D32, 96 | /// Squared 3 dimension. 97 | D33, 98 | /// 3×4 dimension. 99 | D34, 100 | /// 4×2 dimension. 101 | D42, 102 | /// 4×3 dimension. 103 | D43, 104 | /// Squared 4 dimension. 105 | D44, 106 | } 107 | 108 | /// Dimension of a primitive type. 109 | /// 110 | /// Primitive types currently can have one of four dimension: 111 | /// 112 | /// - [`Dim::Scalar`]: designates a scalar value. 113 | /// - [`Dim::D2`]: designates a 2D vector. 114 | /// - [`Dim::D3`]: designates a 3D vector. 115 | /// - [`Dim::D4`]: designates a 4D vector. 116 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 117 | pub enum Dim { 118 | /// Scalar value. 119 | Scalar, 120 | 121 | /// 2D vector. 122 | D2, 123 | 124 | /// 3D vector. 125 | D3, 126 | 127 | /// 4D vector. 128 | D4, 129 | } 130 | 131 | /// Type representation — akin to [`PrimType`] glued with array dimensions, if any. 132 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 133 | pub struct Type { 134 | /// Primitive type, representing a type without array dimensions. 135 | pub(crate) prim_ty: PrimType, 136 | 137 | /// Array dimensions, if any. 138 | /// 139 | /// Dimensions are sorted from outer to inner; i.e. `[[i32; N]; M]`’s dimensions is encoded as `vec![M, N]`. 140 | pub(crate) array_dims: Vec, 141 | } 142 | 143 | /// Primitive supported types. 144 | /// 145 | /// Types without array dimensions are known as _primitive types_ and are exhaustively constructed thanks to 146 | /// [`PrimType`]. 147 | #[non_exhaustive] 148 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 149 | pub enum PrimType { 150 | /// An integral type. 151 | /// 152 | /// The [`Dim`] argument represents the vector dimension — do not confuse it with an array dimension. 153 | Int(Dim), 154 | 155 | /// An unsigned integral type. 156 | /// 157 | /// The [`Dim`] argument represents the vector dimension — do not confuse it with an array dimension. 158 | UInt(Dim), 159 | 160 | /// An floating type. 161 | /// 162 | /// The [`Dim`] argument represents the vector dimension — do not confuse it with an array dimension. 163 | Float(Dim), 164 | 165 | /// A boolean type. 166 | /// 167 | /// The [`Dim`] argument represents the vector dimension — do not confuse it with an array dimension. 168 | Bool(Dim), 169 | 170 | /// A N×M floating matrix. 171 | /// 172 | /// The [`MatrixDim`] provides the information required to know the exact dimension of the matrix. 173 | Matrix(MatrixDim), 174 | } 175 | 176 | /// Class of types that are recognized by the EDSL. 177 | /// 178 | /// Any type implementing this type family is _representable_ in the EDSL. 179 | pub trait ToPrimType { 180 | /// Mapped primitive type. 181 | const PRIM_TYPE: PrimType; 182 | } 183 | 184 | impl ToPrimType for Expr 185 | where 186 | T: ToPrimType, 187 | { 188 | const PRIM_TYPE: PrimType = T::PRIM_TYPE; 189 | } 190 | 191 | macro_rules! impl_ToPrimType { 192 | ($t:ty, $q:ident, $d:ident) => { 193 | impl ToPrimType for $t { 194 | const PRIM_TYPE: PrimType = PrimType::$q(Dim::$d); 195 | } 196 | }; 197 | } 198 | 199 | impl_ToPrimType!(i32, Int, Scalar); 200 | impl_ToPrimType!(u32, UInt, Scalar); 201 | impl_ToPrimType!(f32, Float, Scalar); 202 | impl_ToPrimType!(bool, Bool, Scalar); 203 | impl_ToPrimType!(V2, Int, D2); 204 | impl_ToPrimType!(V2, UInt, D2); 205 | impl_ToPrimType!(V2, Float, D2); 206 | impl_ToPrimType!(V2, Bool, D2); 207 | impl_ToPrimType!(V3, Int, D3); 208 | impl_ToPrimType!(V3, UInt, D3); 209 | impl_ToPrimType!(V3, Float, D3); 210 | impl_ToPrimType!(V3, Bool, D3); 211 | impl_ToPrimType!(V4, Int, D4); 212 | impl_ToPrimType!(V4, UInt, D4); 213 | impl_ToPrimType!(V4, Float, D4); 214 | impl_ToPrimType!(V4, Bool, D4); 215 | 216 | /// Represent a type (primitive type and array dimension) in the EDSL. 217 | /// 218 | /// Any type implementing [`ToType`] is representable in the EDSL. Any type implementing [`ToPrimType`] automatically 219 | /// also implements [`ToType`]. 220 | pub trait ToType { 221 | fn ty() -> Type; 222 | } 223 | 224 | impl ToType for T 225 | where 226 | T: ToPrimType, 227 | { 228 | fn ty() -> Type { 229 | Type { 230 | prim_ty: T::PRIM_TYPE, 231 | array_dims: Vec::new(), 232 | } 233 | } 234 | } 235 | 236 | impl ToType for [T; N] 237 | where 238 | T: ToType, 239 | { 240 | fn ty() -> Type { 241 | let Type { 242 | prim_ty, 243 | array_dims, 244 | } = T::ty(); 245 | let array_dims = once(N).chain(array_dims).collect(); 246 | 247 | Type { 248 | prim_ty, 249 | array_dims, 250 | } 251 | } 252 | } 253 | 254 | /// Trait allowing to create 2D scalar vector ([`V2`])constructors. 255 | /// 2D scalar vectors can be created from either two sole scalars or a single 2D scalar vector (identity function). 256 | /// 257 | /// 258 | /// The `A` type variable represents the arguments type. In the case of several arguments, tuples are used. 259 | /// 260 | /// You are advised to use the [`vec2!`](vec2) macro instead as the interface of this function is not really 261 | /// user-friendly. 262 | pub trait Vec2 { 263 | /// Make a [`V2`] from `A`. 264 | fn vec2(args: A) -> Self; 265 | } 266 | 267 | impl Vec2<(Expr, Expr)> for Expr> { 268 | fn vec2(args: (Expr, Expr)) -> Self { 269 | let (x, y) = args; 270 | Expr::new(ErasedExpr::FunCall( 271 | ErasedFunHandle::Vec2, 272 | vec![x.erased, y.erased], 273 | )) 274 | } 275 | } 276 | 277 | /// Trait allowing to create 3D scalar vector ([`V3`])constructors. 278 | /// 279 | /// 3D scalar vectors can be created from either three sole scalars, a single 2D scalar vector with a single scalar or 280 | /// a single 3D scalar vector (identity function). 281 | /// 282 | /// The `A` type variable represents the arguments type. In the case of several arguments, tuples are used. 283 | /// 284 | /// You are advised to use the [`vec3!`](vec3) macro instead as the interface of this function is not really 285 | /// user-friendly. 286 | pub trait Vec3 { 287 | /// Make a [`V3`] from `A`. 288 | fn vec3(args: A) -> Self; 289 | } 290 | 291 | impl Vec3<(Expr>, Expr)> for Expr> { 292 | fn vec3(args: (Expr>, Expr)) -> Self { 293 | let (xy, z) = args; 294 | Expr::new(ErasedExpr::FunCall( 295 | ErasedFunHandle::Vec3, 296 | vec![xy.erased, z.erased], 297 | )) 298 | } 299 | } 300 | 301 | impl Vec3<(Expr, Expr, Expr)> for Expr> { 302 | fn vec3(args: (Expr, Expr, Expr)) -> Self { 303 | let (x, y, z) = args; 304 | Expr::new(ErasedExpr::FunCall( 305 | ErasedFunHandle::Vec3, 306 | vec![x.erased, y.erased, z.erased], 307 | )) 308 | } 309 | } 310 | 311 | /// Trait allowing to create 4D scalar vector ([`V4`])constructors. 312 | /// 313 | /// 4D scalar vectors can be created from either four sole scalars, a single 3D scalar vector with a single scalar, 314 | /// two 2D scalar vectors, a 2D scalar vector and a sole scalar or a single 4D scalar vector (identity function). 315 | /// 316 | /// The `A` type variable represents the arguments type. In the case of several arguments, tuples are used. 317 | /// 318 | /// You are advised to use the [`vec4!`](vec4) macro instead as the interface of this function is not really 319 | /// user-friendly. 320 | pub trait Vec4 { 321 | /// Make a [`V4`] from `A`. 322 | fn vec4(args: A) -> Self; 323 | } 324 | 325 | impl Vec4<(Expr>, Expr)> for Expr> { 326 | fn vec4(args: (Expr>, Expr)) -> Self { 327 | let (xyz, w) = args; 328 | Expr::new(ErasedExpr::FunCall( 329 | ErasedFunHandle::Vec4, 330 | vec![xyz.erased, w.erased], 331 | )) 332 | } 333 | } 334 | 335 | impl Vec4<(Expr>, Expr>)> for Expr> { 336 | fn vec4(args: (Expr>, Expr>)) -> Self { 337 | let (xy, zw) = args; 338 | Expr::new(ErasedExpr::FunCall( 339 | ErasedFunHandle::Vec4, 340 | vec![xy.erased, zw.erased], 341 | )) 342 | } 343 | } 344 | 345 | impl<'a, T> Vec4<(Expr>, Expr, Expr)> for Expr> { 346 | fn vec4(args: (Expr>, Expr, Expr)) -> Self { 347 | let (xy, z, w) = args; 348 | Expr::new(ErasedExpr::FunCall( 349 | ErasedFunHandle::Vec4, 350 | vec![xy.erased, z.erased, w.erased], 351 | )) 352 | } 353 | } 354 | 355 | impl<'a, T> Vec4<(Expr, Expr, Expr, Expr)> for Expr> { 356 | fn vec4(args: (Expr, Expr, Expr, Expr)) -> Self { 357 | let (x, y, z, w) = args; 358 | Expr::new(ErasedExpr::FunCall( 359 | ErasedFunHandle::Vec4, 360 | vec![x.erased, y.erased, z.erased, w.erased], 361 | )) 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /shades/src/var.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{ErasedExpr, Expr}, 3 | scope::ScopedHandle, 4 | }; 5 | use std::ops; 6 | 7 | /// Mutable variable. 8 | /// 9 | /// A [`Var`] is akin to an [`Expr`] that can be mutated. You can go from a [`Var`] to an [`Expr`] via 10 | /// either the [`From`] or [`Var::to_expr`] method. 11 | /// 12 | /// Variables, because they allow mutations, allow to write more complicated shader functions. Also, lots of graphics 13 | /// pipelines’ properties are variables you will have to write to, such as [`VertexShaderEnv::position`]. 14 | #[derive(Clone, Debug)] 15 | pub struct Var(pub Expr) 16 | where 17 | T: ?Sized; 18 | 19 | impl<'a, T> From<&'a Var> for Var 20 | where 21 | T: ?Sized, 22 | { 23 | fn from(v: &'a Self) -> Self { 24 | Var(v.0.clone()) 25 | } 26 | } 27 | 28 | impl From> for Expr 29 | where 30 | T: ?Sized, 31 | { 32 | fn from(v: Var) -> Self { 33 | v.0 34 | } 35 | } 36 | 37 | impl<'a, T> From<&'a Var> for Expr 38 | where 39 | T: ?Sized, 40 | { 41 | fn from(v: &'a Var) -> Self { 42 | v.0.clone() 43 | } 44 | } 45 | 46 | impl Var 47 | where 48 | T: ?Sized, 49 | { 50 | /// Create a new [`Var`] from a [`ScopedHandle`]. 51 | pub(crate) const fn new(handle: ScopedHandle) -> Self { 52 | Self(Expr::new(ErasedExpr::Var(handle))) 53 | } 54 | 55 | /// Coerce [`Var`] into [`Expr`]. 56 | /// 57 | /// Remember that doing so will move the [`Var`]. `clone` it if you want to preserve the source variable. 58 | /// 59 | /// > Note: use this function only when necessary. Lots of functions will accept both [`Expr`] and [`Var`], 60 | /// > performing the coercion for you automatically. 61 | /// 62 | /// # Return 63 | /// 64 | /// The expression representation of [`Var`], allowing to pass the variable to functions or expressions that don’t 65 | /// easily coerce it automatically to [`Expr`] already. 66 | pub fn to_expr(&self) -> Expr { 67 | self.0.clone() 68 | } 69 | } 70 | 71 | impl Var<[T]> { 72 | pub fn at(&self, index: Expr) -> Var { 73 | Var(self.to_expr().at(index)) 74 | } 75 | } 76 | 77 | impl Var<[T; N]> { 78 | pub fn at(&self, index: Expr) -> Var { 79 | Var(self.to_expr().at(index)) 80 | } 81 | } 82 | 83 | impl ops::Deref for Var 84 | where 85 | T: ?Sized, 86 | { 87 | type Target = Expr; 88 | 89 | fn deref(&self) -> &Self::Target { 90 | &self.0 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /shades/src/writer.rs: -------------------------------------------------------------------------------- 1 | //! All available _shades -> lang_ writers. 2 | pub mod glsl; 3 | -------------------------------------------------------------------------------- /shades/tests/shades.rs: -------------------------------------------------------------------------------- 1 | use shades::{ 2 | env::Environment, expr::Expr, input::Inputs, output::Outputs, stage::VS, types::ToType, 3 | }; 4 | use shades_edsl::shades; 5 | 6 | #[derive(Debug)] 7 | struct TestInput; 8 | 9 | impl Inputs for TestInput { 10 | type In = (); 11 | 12 | fn input() -> Self::In { 13 | () 14 | } 15 | 16 | fn input_set() -> Vec<(u16, shades::types::Type)> { 17 | Vec::new() 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | struct TestOutput; 23 | 24 | impl Outputs for TestOutput { 25 | type Out = (); 26 | 27 | fn output() -> Self::Out { 28 | () 29 | } 30 | 31 | fn output_set() -> Vec<(u16, shades::types::Type)> { 32 | Vec::new() 33 | } 34 | } 35 | 36 | struct TestEnv { 37 | t: Expr, 38 | } 39 | 40 | impl Environment for TestEnv { 41 | type Env = TestEnv; 42 | 43 | fn env() -> Self::Env { 44 | TestEnv { 45 | t: Expr::new_env("t"), 46 | } 47 | } 48 | 49 | fn env_set() -> Vec<(String, shades::types::Type)> { 50 | vec![("t".to_owned(), ::ty())] 51 | } 52 | } 53 | 54 | /// Test the main shades! macro. 55 | #[test] 56 | fn test_shades() { 57 | let stage = shades! { VS |_input: TestInput, _output: TestOutput, env: TestEnv| { 58 | fn add(a: f32, b: f32) -> f32 { 59 | let x = 3.; 60 | let y = 2.; 61 | let z = 10.; 62 | let w = [1., 2.]; 63 | 64 | x = 10.; 65 | y *= z; 66 | 67 | while true { 68 | x += 1.; 69 | } 70 | 71 | (a + b * 2. * a) * env.t + w[1] 72 | } 73 | 74 | fn main() { 75 | let _x = add(1., 2.); 76 | } 77 | }}; 78 | 79 | let expected = r#"uniform float t; 80 | 81 | float fun_0(float arg_0, float arg_1) { 82 | float var_0_0 = 3.; 83 | float var_0_1 = 2.; 84 | float var_0_2 = 10.; 85 | float[2] var_0_3 = float[2](1.,2.); 86 | var_0_0 = 10.; 87 | var_0_1 *= var_0_2; 88 | while (true) { 89 | var_0_0 += 1.; 90 | } 91 | return (((arg_0 + ((arg_1 * 2.) * arg_0)) * t) + var_0_3[1]); 92 | 93 | } 94 | 95 | void main() { 96 | float var_0_0 = fun_0(1., 2.); 97 | }"#; 98 | 99 | assert_eq!( 100 | shades::writer::glsl::write_shader_to_str(&stage).unwrap(), 101 | expected 102 | ); 103 | } 104 | --------------------------------------------------------------------------------