diff options
author | HampusM <hampus@hampusmat.com> | 2025-09-19 16:36:57 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2025-10-02 16:55:33 +0200 |
commit | ea1d70c8c28e3b96da6264021fa1c62e28fcd8e4 (patch) | |
tree | 62ae9b75ee84602899b51483ed26fa664df36888 | |
parent | 0008b374c7f3a9ef6b30ea31a4a8c98bce64649f (diff) |
feat: add OpenGL bindings crate
-rw-r--r-- | Cargo.lock | 570 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | opengl-bindings/Cargo.toml | 65 | ||||
-rw-r--r-- | opengl-bindings/build.rs | 107 | ||||
-rw-r--r-- | opengl-bindings/src/buffer.rs | 167 | ||||
-rw-r--r-- | opengl-bindings/src/data_types.rs | 37 | ||||
-rw-r--r-- | opengl-bindings/src/debug.rs | 161 | ||||
-rw-r--r-- | opengl-bindings/src/lib.rs | 119 | ||||
-rw-r--r-- | opengl-bindings/src/misc.rs | 190 | ||||
-rw-r--r-- | opengl-bindings/src/shader.rs | 366 | ||||
-rw-r--r-- | opengl-bindings/src/texture.rs | 236 | ||||
-rw-r--r-- | opengl-bindings/src/vertex_array.rs | 260 |
12 files changed, 2257 insertions, 23 deletions
@@ -36,6 +36,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -64,7 +70,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.100", ] [[package]] @@ -78,13 +84,13 @@ dependencies = [ "clang-sys", "itertools", "log", - "prettyplease", + "prettyplease 0.2.31", "proc-macro2", "quote", "regex", "rustc-hash 2.1.1", "shlex", - "syn", + "syn 2.0.100", ] [[package]] @@ -133,6 +139,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -251,6 +272,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] name = "ecs" version = "0.1.0" dependencies = [ @@ -272,7 +312,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "toml", ] @@ -305,6 +345,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "extension-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +dependencies = [ + "ext-trait", +] + +[[package]] +name = "extern-c" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" + +[[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -339,6 +414,17 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] name = "gl" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -376,6 +462,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.9.0", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] name = "graphviz-sys" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -427,9 +567,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -443,7 +583,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -518,6 +658,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] +name = "macro_rules_attribute" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" + +[[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -578,6 +734,55 @@ dependencies = [ ] [[package]] +name = "objc2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] name = "once_cell" version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -590,6 +795,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] +name = "opengl-bindings" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags 2.9.0", + "gl_generator", + "glutin", + "safer-ffi", + "thiserror", + "toml", + "util-macros", +] + +[[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -637,6 +856,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -650,13 +875,41 @@ dependencies = [ ] [[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] name = "prettyplease" version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.100", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", ] [[package]] @@ -678,6 +931,42 @@ dependencies = [ ] [[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] name = "redox_syscall" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -743,12 +1032,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] +name = "safer-ffi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" +dependencies = [ + "extern-c", + "libc", + "macro_rules_attribute", + "paste", + "safer_ffi-proc_macros", + "scopeguard", + "stabby", + "uninit", + "unwind_safe", + "with_builtin_macros", +] + +[[package]] +name = "safer_ffi-proc_macros" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" +dependencies = [ + "macro_rules_attribute", + "prettyplease 0.1.25", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -764,6 +1099,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] name = "seq-macro" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -771,22 +1112,32 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -811,6 +1162,12 @@ dependencies = [ ] [[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -838,6 +1195,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] +name = "stabby" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8" +dependencies = [ + "rustversion", + "stabby-abi", +] + +[[package]] +name = "stabby-abi" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a" +dependencies = [ + "rustc_version", + "rustversion", + "sha2-const-stable", + "stabby-macros", +] + +[[package]] +name = "stabby-macros" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "rand", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -865,7 +1268,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -896,8 +1299,8 @@ checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.8", + "toml_edit 0.22.24", ] [[package]] @@ -910,6 +1313,15 @@ dependencies = [ ] [[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -918,7 +1330,28 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.8", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ "winnow", ] @@ -941,7 +1374,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -977,12 +1410,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] +name = "uninit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" +dependencies = [ + "extension-traits", +] + +[[package]] +name = "unwind_safe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" + +[[package]] name = "util-macros" version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1006,6 +1454,24 @@ dependencies = [ ] [[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1027,7 +1493,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1038,6 +1504,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" @@ -1111,15 +1586,66 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] +name = "with_builtin_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] name = "xml-rs" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["glfw", "engine", "ecs", "ecs-macros", "util-macros"] +members = ["glfw", "engine", "ecs", "ecs-macros", "util-macros", "opengl-bindings"] [dependencies] engine = { path = "./engine" } diff --git a/opengl-bindings/Cargo.toml b/opengl-bindings/Cargo.toml new file mode 100644 index 0000000..8251642 --- /dev/null +++ b/opengl-bindings/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "opengl-bindings" +version = "0.1.0" +edition = "2021" + +[dependencies] +glutin = "0.32.3" +thiserror = "1.0.49" +safer-ffi = "0.1.13" +bitflags = "2.4.0" +util-macros = { path = "../util-macros" } + +[build-dependencies] +gl_generator = "=0.14.0" +toml = "0.8.12" +anyhow = "1.0.100" + +[package.metadata.build] +gl_commands = [ + "CreateBuffers", + "NamedBufferData", + "NamedBufferSubData", + "CreateVertexArrays", + "DrawArrays", + "DrawElements", + "VertexArrayElementBuffer", + "VertexArrayVertexBuffer", + "EnableVertexArrayAttrib", + "VertexArrayAttribFormat", + "VertexArrayAttribBinding", + "BindVertexArray", + "TextureStorage2D", + "TextureSubImage2D", + "DeleteTextures", + "GenerateTextureMipmap", + "TextureParameteri", + "CreateTextures", + "BindTextureUnit", + "DeleteShader", + "CreateShader", + "ShaderSource", + "CompileShader", + "GetShaderiv", + "GetShaderInfoLog", + "LinkProgram", + "GetProgramiv", + "CreateProgram", + "AttachShader", + "UseProgram", + "GetUniformLocation", + "ProgramUniform1f", + "ProgramUniform1i", + "ProgramUniform3f", + "ProgramUniformMatrix4fv", + "GetProgramInfoLog", + "DeleteProgram", + "Viewport", + "Clear", + "PolygonMode", + "Enable", + "Disable", + "GetIntegerv", + "DebugMessageCallback", + "DebugMessageControl" +] diff --git a/opengl-bindings/build.rs b/opengl-bindings/build.rs new file mode 100644 index 0000000..060472c --- /dev/null +++ b/opengl-bindings/build.rs @@ -0,0 +1,107 @@ +use std::collections::HashSet; +use std::env; +use std::fs::File; +use std::path::{Path, PathBuf}; + +use anyhow::anyhow; +use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator}; + +fn main() -> Result<(), anyhow::Error> +{ + println!("cargo::rerun-if-changed=build.rs"); + println!("cargo::rerun-if-changed=Cargo.toml"); + + let dest = env::var("OUT_DIR")?; + + let mut file = File::create(Path::new(&dest).join("bindings.rs"))?; + + let mut registry = Registry::new(Api::Gl, (4, 6), Profile::Core, Fallbacks::All, []); + + let mut build_metadata = get_build_metadata()?; + + filter_gl_commands(&mut registry, &mut build_metadata)?; + + registry.write_bindings(StructGenerator, &mut file)?; + + Ok(()) +} + +fn filter_gl_commands( + registry: &mut Registry, + build_metadata: &mut BuildMetadata, +) -> Result<(), anyhow::Error> +{ + registry + .cmds + .retain(|command| build_metadata.gl_commands.remove(&command.proto.ident)); + + if !build_metadata.gl_commands.is_empty() { + return Err(anyhow!( + "Invalid GL commands: [{}]", + build_metadata + .gl_commands + .iter() + .cloned() + .collect::<Vec<_>>() + .join(", ") + )); + } + + Ok(()) +} + +fn get_build_metadata() -> Result<BuildMetadata, anyhow::Error> +{ + let manifest_path = PathBuf::from(std::env::var("CARGO_MANIFEST_PATH")?); + + let manifest = std::fs::read_to_string(manifest_path)?.parse::<toml::Table>()?; + + let package = match manifest + .get("package") + .ok_or_else(|| anyhow!("Manifest does not have a package table"))? + { + toml::Value::Table(package) => Ok(package), + _ => Err(anyhow!("Manifest package must be a table")), + }?; + + let metadata = match package + .get("metadata") + .ok_or_else(|| anyhow!("Manifest does not have a package.metadata table"))? + { + toml::Value::Table(metadata) => Ok(metadata), + _ => Err(anyhow!("Manifest package.metadata must be a table")), + }?; + + let build_metadata = match metadata + .get("build") + .ok_or_else(|| anyhow!("Manifest does not have a package.metadata.build table"))? + { + toml::Value::Table(build_metadata) => Ok(build_metadata), + _ => Err(anyhow!("Manifest package.metadata.build must be a table")), + }?; + + let gl_command_values = match build_metadata.get("gl_commands").ok_or_else(|| { + anyhow!("Manifest does not have a package.metadata.build.gl_commands array") + })? { + toml::Value::Array(gl_commands) => Ok(gl_commands), + _ => Err(anyhow!( + "Manifest package.metadata.build.gl_commands must be a array" + )), + }?; + + let gl_commands = gl_command_values + .iter() + .map(|gl_command_val| match gl_command_val { + toml::Value::String(gl_command) => Ok(gl_command.clone()), + _ => Err(anyhow!("GL command must be a string")), + }) + .collect::<Result<HashSet<_>, _>>()?; + + Ok(BuildMetadata { gl_commands }) +} + +#[derive(Debug)] +struct BuildMetadata +{ + gl_commands: HashSet<String>, +} diff --git a/opengl-bindings/src/buffer.rs b/opengl-bindings/src/buffer.rs new file mode 100644 index 0000000..c64ec8d --- /dev/null +++ b/opengl-bindings/src/buffer.rs @@ -0,0 +1,167 @@ +use std::marker::PhantomData; +use std::mem::size_of_val; +use std::ptr::null; + +use safer_ffi::layout::ReprC; + +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Buffer<Item: ReprC> +{ + buf: crate::sys::types::GLuint, + _pd: PhantomData<Item>, +} + +impl<Item: ReprC> Buffer<Item> +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut buffer = crate::sys::types::GLuint::default(); + + unsafe { + current_context.fns().CreateBuffers(1, &raw mut buffer); + }; + + Self { buf: buffer, _pd: PhantomData } + } + + /// Stores items in this buffer. + /// + /// # Errors + /// Returns `Err` if the total size (in bytes) is too large. + pub fn store( + &self, + current_context: &CurrentContextWithFns<'_>, + items: &[Item], + usage: Usage, + ) -> Result<(), Error> + { + let total_size = size_of_val(items); + + let total_size: crate::sys::types::GLsizeiptr = + total_size + .try_into() + .map_err(|_| Error::TotalItemsSizeTooLarge { + total_size, + max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, + })?; + + unsafe { + current_context.fns().NamedBufferData( + self.buf, + total_size, + items.as_ptr().cast(), + usage.into_gl(), + ); + } + + Ok(()) + } + + /// Maps the values in the `values` slice into `Item`s which is stored into this + /// buffer. + /// + /// # Errors + /// Returns `Err` if the total size (in bytes) is too large. + pub fn store_mapped<Value>( + &self, + current_context: &CurrentContextWithFns<'_>, + values: &[Value], + usage: Usage, + mut map_func: impl FnMut(&Value) -> Item, + ) -> Result<(), Error> + { + let item_size: crate::sys::types::GLsizeiptr = const { + assert!(size_of::<Item>() <= crate::sys::types::GLsizeiptr::MAX as usize); + + size_of::<Item>().cast_signed() + }; + + let total_size = size_of::<Item>() * values.len(); + + let total_size: crate::sys::types::GLsizeiptr = + total_size + .try_into() + .map_err(|_| Error::TotalItemsSizeTooLarge { + total_size, + max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, + })?; + + unsafe { + current_context.fns().NamedBufferData( + self.buf, + total_size, + null(), + usage.into_gl(), + ); + } + + for (index, value) in values.iter().enumerate() { + let item = map_func(value); + + let offset = index * size_of::<Item>(); + + let Ok(offset_casted) = crate::sys::types::GLintptr::try_from(offset) else { + unreachable!(); // Reason: The total size can be casted to a GLintptr + // (done above) so offsets should be castable as well + }; + + unsafe { + current_context.fns().NamedBufferSubData( + self.buf, + offset_casted, + item_size, + (&raw const item).cast(), + ); + } + } + + Ok(()) + } + + pub(crate) fn object(&self) -> crate::sys::types::GLuint + { + self.buf + } +} + +/// Buffer usage. +#[derive(Debug)] +pub enum Usage +{ + /// The buffer data is set only once and used by the GPU at most a few times. + Stream, + + /// The buffer data is set only once and used many times. + Static, + + /// The buffer data is changed a lot and used many times. + Dynamic, +} + +impl Usage +{ + fn into_gl(self) -> crate::sys::types::GLenum + { + match self { + Self::Stream => crate::sys::STREAM_DRAW, + Self::Static => crate::sys::STATIC_DRAW, + Self::Dynamic => crate::sys::DYNAMIC_DRAW, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error( + "Total size of items ({total_size}) is too large. Must be < {max_total_size}" + )] + TotalItemsSizeTooLarge + { + total_size: usize, + max_total_size: usize, + }, +} diff --git a/opengl-bindings/src/data_types.rs b/opengl-bindings/src/data_types.rs new file mode 100644 index 0000000..7ead0ab --- /dev/null +++ b/opengl-bindings/src/data_types.rs @@ -0,0 +1,37 @@ +use safer_ffi::derive_ReprC; +use safer_ffi::layout::ReprC; + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Matrix<Value: ReprC, const ROWS: usize, const COLUMNS: usize> +{ + /// Items must be layed out this way for it to work with OpenGL shaders. + pub items: [[Value; ROWS]; COLUMNS], +} + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Vec3<Value: ReprC> +{ + pub x: Value, + pub y: Value, + pub z: Value, +} + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Vec2<Value> +{ + pub x: Value, + pub y: Value, +} + +#[derive(Debug, Clone)] +pub struct Dimens<Value> +{ + pub width: Value, + pub height: Value, +} diff --git a/opengl-bindings/src/debug.rs b/opengl-bindings/src/debug.rs new file mode 100644 index 0000000..a9369a4 --- /dev/null +++ b/opengl-bindings/src/debug.rs @@ -0,0 +1,161 @@ +use std::ffi::c_void; +use std::io::{stderr, Write}; +use std::mem::transmute; +use std::panic::catch_unwind; + +use util_macros::FromRepr; + +use crate::CurrentContextWithFns; + +pub fn set_debug_message_callback( + current_context: &CurrentContextWithFns<'_>, + cb: MessageCallback, +) +{ + unsafe { + current_context + .fns() + .DebugMessageCallback(Some(debug_message_cb), cb as *mut c_void); + } +} + +/// Sets debug message parameters. +/// +/// # Errors +/// Returns `Err` if `ids` contains too many ids. +pub fn set_debug_message_control( + current_context: &CurrentContextWithFns<'_>, + source: Option<MessageSource>, + ty: Option<MessageType>, + severity: Option<MessageSeverity>, + ids: &[u32], + ids_action: MessageIdsAction, +) -> Result<(), SetDebugMessageControlError> +{ + let ids_len: crate::sys::types::GLsizei = + ids.len() + .try_into() + .map_err(|_| SetDebugMessageControlError::TooManyIds { + id_cnt: ids.len(), + max_id_cnt: crate::sys::types::GLsizei::MAX as usize, + })?; + + unsafe { + current_context.fns().DebugMessageControl( + source.map_or(crate::sys::DONT_CARE, |source| source as u32), + ty.map_or(crate::sys::DONT_CARE, |ty| ty as u32), + severity.map_or(crate::sys::DONT_CARE, |severity| severity as u32), + ids_len, + ids.as_ptr(), + ids_action as u8, + ); + } + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum SetDebugMessageControlError +{ + #[error("Too many ids provided ({id_cnt}). Must be < {max_id_cnt}")] + TooManyIds + { + id_cnt: usize, max_id_cnt: usize + }, +} + +pub type MessageCallback = fn( + source: MessageSource, + ty: MessageType, + id: u32, + severity: MessageSeverity, + message: &str, +); + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] // GLboolean = u8 +pub enum MessageIdsAction +{ + Enable = crate::sys::TRUE, + Disable = crate::sys::FALSE, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageSource +{ + Api = crate::sys::DEBUG_SOURCE_API, + WindowSystem = crate::sys::DEBUG_SOURCE_WINDOW_SYSTEM, + ShaderCompiler = crate::sys::DEBUG_SOURCE_SHADER_COMPILER, + ThirdParty = crate::sys::DEBUG_SOURCE_THIRD_PARTY, + Application = crate::sys::DEBUG_SOURCE_APPLICATION, + Other = crate::sys::DEBUG_SOURCE_OTHER, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageType +{ + DeprecatedBehavior = crate::sys::DEBUG_TYPE_DEPRECATED_BEHAVIOR, + Error = crate::sys::DEBUG_TYPE_ERROR, + Marker = crate::sys::DEBUG_TYPE_MARKER, + Other = crate::sys::DEBUG_TYPE_OTHER, + Performance = crate::sys::DEBUG_TYPE_PERFORMANCE, + PopGroup = crate::sys::DEBUG_TYPE_POP_GROUP, + PushGroup = crate::sys::DEBUG_TYPE_PUSH_GROUP, + Portability = crate::sys::DEBUG_TYPE_PORTABILITY, + UndefinedBehavior = crate::sys::DEBUG_TYPE_UNDEFINED_BEHAVIOR, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageSeverity +{ + High = crate::sys::DEBUG_SEVERITY_HIGH, + Medium = crate::sys::DEBUG_SEVERITY_MEDIUM, + Low = crate::sys::DEBUG_SEVERITY_LOW, + Notification = crate::sys::DEBUG_SEVERITY_NOTIFICATION, +} + +extern "system" fn debug_message_cb( + source: crate::sys::types::GLenum, + ty: crate::sys::types::GLenum, + id: crate::sys::types::GLuint, + severity: crate::sys::types::GLenum, + message_length: crate::sys::types::GLsizei, + message: *const crate::sys::types::GLchar, + user_cb: *mut c_void, +) +{ + let user_cb = unsafe { transmute::<*mut c_void, MessageCallback>(user_cb) }; + + let Ok(msg_length) = usize::try_from(message_length) else { + return; + }; + + // Unwinds are catched because unwinding from Rust code into foreign code is UB. + let res = catch_unwind(|| { + let msg_source = MessageSource::from_repr(source).unwrap(); + let msg_type = MessageType::from_repr(ty).unwrap(); + let msg_severity = MessageSeverity::from_repr(severity).unwrap(); + + // SAFETY: The received message should be a valid ASCII string + let message = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + message.cast(), + msg_length, + )) + }; + + user_cb(msg_source, msg_type, id, msg_severity, message); + }); + + if res.is_err() { + // eprintln is not used since it can panic and unwinds are unwanted because + // unwinding from Rust code into foreign code is UB. + stderr() + .write_all(b"ERROR: Panic in debug message callback") + .ok(); + println!(); + } +} diff --git a/opengl-bindings/src/lib.rs b/opengl-bindings/src/lib.rs new file mode 100644 index 0000000..7fd9933 --- /dev/null +++ b/opengl-bindings/src/lib.rs @@ -0,0 +1,119 @@ +#![deny(clippy::all, clippy::pedantic)] +use std::ffi::CString; +use std::process::abort; + +use glutin::context::{NotCurrentContext, PossiblyCurrentContext}; +use glutin::display::GetGlDisplay; +use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext}; +use glutin::surface::{Surface, SurfaceTypeTrait}; + +pub mod buffer; +pub mod data_types; +pub mod debug; +pub mod misc; +pub mod shader; +pub mod texture; +pub mod vertex_array; + +pub struct ContextWithFns +{ + context: PossiblyCurrentContext, + fns: Box<sys::Gl>, +} + +impl ContextWithFns +{ + /// Returns a new `ContextWithFns`. + /// + /// # Errors + /// Returns `Err` if making this context current fails. + pub fn new<SurfaceType: SurfaceTypeTrait>( + context: NotCurrentContext, + surface: &Surface<SurfaceType>, + ) -> Result<Self, Error> + { + let context = context + .make_current(surface) + .map_err(Error::MakeContextCurrentFailed)?; + + let display = context.display(); + + let gl = sys::Gl::load_with(|symbol| { + let Ok(symbol) = CString::new(symbol) else { + eprintln!("GL symbol contains nul byte"); + abort(); + }; + + display.get_proc_address(&symbol) + }); + + Ok(Self { context, fns: Box::new(gl) }) + } + + /// Attempts to make this context current. + /// + /// # Errors + /// Returns `Err` if making this context current fails. + pub fn make_current<SurfaceType: SurfaceTypeTrait>( + &self, + surface: &Surface<SurfaceType>, + ) -> Result<CurrentContextWithFns<'_>, Error> + { + if !self.context.is_current() { + self.context + .make_current(surface) + .map_err(Error::MakeContextCurrentFailed)?; + } + + Ok(CurrentContextWithFns { ctx: self }) + } + + #[must_use] + pub fn context(&self) -> &PossiblyCurrentContext + { + &self.context + } + + #[must_use] + pub fn context_mut(&mut self) -> &mut PossiblyCurrentContext + { + &mut self.context + } +} + +pub struct CurrentContextWithFns<'ctx> +{ + ctx: &'ctx ContextWithFns, +} + +impl CurrentContextWithFns<'_> +{ + #[inline] + pub(crate) fn fns(&self) -> &sys::Gl + { + &self.ctx.fns + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to make context current")] + MakeContextCurrentFailed(#[source] glutin::error::Error), +} + +mod sys +{ + #![allow( + clippy::missing_safety_doc, + clippy::missing_transmute_annotations, + clippy::too_many_arguments, + clippy::unused_unit, + clippy::upper_case_acronyms, + clippy::doc_markdown, + clippy::unreadable_literal, + unsafe_op_in_unsafe_fn + )] + + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} diff --git a/opengl-bindings/src/misc.rs b/opengl-bindings/src/misc.rs new file mode 100644 index 0000000..bb54c1a --- /dev/null +++ b/opengl-bindings/src/misc.rs @@ -0,0 +1,190 @@ +use bitflags::bitflags; + +use crate::data_types::{Dimens, Vec2}; +use crate::CurrentContextWithFns; + +/// Sets the viewport. +/// +/// The `u32` values in `position` and `size` must fit in `i32`s. +/// +/// # Errors +/// Returns `Err` if any value in `position` or `size` does not fit into a `i32`. +pub fn set_viewport( + current_context: &CurrentContextWithFns<'_>, + position: &Vec2<u32>, + size: &Dimens<u32>, +) -> Result<(), SetViewportError> +{ + let position = Vec2::<crate::sys::types::GLint> { + x: position.x.try_into().map_err(|_| { + SetViewportError::PositionXValueTooLarge { + value: position.x, + max_value: crate::sys::types::GLint::MAX as u32, + } + })?, + y: position.y.try_into().map_err(|_| { + SetViewportError::PositionYValueTooLarge { + value: position.y, + max_value: crate::sys::types::GLint::MAX as u32, + } + })?, + }; + + let size = Dimens::<crate::sys::types::GLsizei> { + width: size.width.try_into().map_err(|_| { + SetViewportError::SizeWidthValueTooLarge { + value: size.width, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + height: size.height.try_into().map_err(|_| { + SetViewportError::SizeHeightValueTooLarge { + value: size.height, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + }; + + unsafe { + current_context + .fns() + .Viewport(position.x, position.y, size.width, size.height); + } + + Ok(()) +} + +pub fn clear_buffers(current_context: &CurrentContextWithFns<'_>, mask: BufferClearMask) +{ + unsafe { + current_context.fns().Clear(mask.bits()); + } +} + +pub fn set_polygon_mode( + current_context: &CurrentContextWithFns<'_>, + face: impl Into<PolygonModeFace>, + mode: impl Into<PolygonMode>, +) +{ + unsafe { + current_context + .fns() + .PolygonMode(face.into() as u32, mode.into() as u32); + } +} + +pub fn enable(current_context: &CurrentContextWithFns<'_>, capacity: Capability) +{ + unsafe { + current_context.fns().Enable(capacity as u32); + } +} + +pub fn disable(current_context: &CurrentContextWithFns<'_>, capability: Capability) +{ + unsafe { + current_context.fns().Disable(capability as u32); + } +} + +pub fn set_enabled( + current_context: &CurrentContextWithFns<'_>, + capability: Capability, + enabled: bool, +) +{ + if enabled { + enable(current_context, capability); + } else { + disable(current_context, capability); + } +} + +#[must_use] +pub fn get_context_flags(current_context: &CurrentContextWithFns<'_>) -> ContextFlags +{ + let mut context_flags = crate::sys::types::GLint::default(); + + unsafe { + current_context + .fns() + .GetIntegerv(crate::sys::CONTEXT_FLAGS, &raw mut context_flags); + } + + ContextFlags::from_bits_truncate(context_flags.cast_unsigned()) +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct BufferClearMask: u32 { + const COLOR = crate::sys::COLOR_BUFFER_BIT; + const DEPTH = crate::sys::DEPTH_BUFFER_BIT; + const STENCIL = crate::sys::STENCIL_BUFFER_BIT; + } +} + +#[derive(Debug)] +#[repr(u32)] +pub enum Capability +{ + DepthTest = crate::sys::DEPTH_TEST, + MultiSample = crate::sys::MULTISAMPLE, + DebugOutput = crate::sys::DEBUG_OUTPUT, + DebugOutputSynchronous = crate::sys::DEBUG_OUTPUT_SYNCHRONOUS, +} + +#[derive(Debug)] +#[repr(u32)] +pub enum PolygonMode +{ + Point = crate::sys::POINT, + Line = crate::sys::LINE, + Fill = crate::sys::FILL, +} + +#[derive(Debug)] +#[repr(u32)] +pub enum PolygonModeFace +{ + Front = crate::sys::FRONT, + Back = crate::sys::BACK, + FrontAndBack = crate::sys::FRONT_AND_BACK, +} + +bitflags! { +#[derive(Debug, Clone, Copy)] +pub struct ContextFlags: u32 { + const FORWARD_COMPATIBLE = crate::sys::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; + const DEBUG = crate::sys::CONTEXT_FLAG_DEBUG_BIT; + const ROBUST_ACCESS = crate::sys::CONTEXT_FLAG_ROBUST_ACCESS_BIT; +} +} + +#[derive(Debug, thiserror::Error)] +pub enum SetViewportError +{ + #[error("Position X value ({value}) is too large. Must be < {max_value}")] + PositionXValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Position Y value ({value}) is too large. Must be < {max_value}")] + PositionYValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Size width value ({value}) is too large. Must be < {max_value}")] + SizeWidthValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Size height value ({value}) is too large. Must be < {max_value}")] + SizeHeightValueTooLarge + { + value: u32, max_value: u32 + }, +} diff --git a/opengl-bindings/src/shader.rs b/opengl-bindings/src/shader.rs new file mode 100644 index 0000000..5ed66a2 --- /dev/null +++ b/opengl-bindings/src/shader.rs @@ -0,0 +1,366 @@ +use std::ffi::CStr; +use std::ptr::null_mut; + +use safer_ffi::layout::ReprC; + +use crate::data_types::{Matrix, Vec3}; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Shader +{ + shader: crate::sys::types::GLuint, +} + +impl Shader +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>, kind: Kind) -> Self + { + let shader = unsafe { + current_context + .fns() + .CreateShader(kind as crate::sys::types::GLenum) + }; + + Self { shader } + } + + /// Sets the source code of this shader. + /// + /// # Errors + /// Returns `Err` if `source` is not ASCII. + pub fn set_source( + &self, + current_context: &CurrentContextWithFns<'_>, + source: &str, + ) -> Result<(), Error> + { + if !source.is_ascii() { + return Err(Error::SourceNotAscii); + } + + let length: crate::sys::types::GLint = + source.len().try_into().map_err(|_| Error::SourceTooLarge { + length: source.len(), + max_length: crate::sys::types::GLint::MAX as usize, + })?; + + unsafe { + current_context.fns().ShaderSource( + self.shader, + 1, + &source.as_ptr().cast(), + &raw const length, + ); + } + + Ok(()) + } + + /// Compiles this shader. + /// + /// # Errors + /// Returns `Err` if compiling fails. + pub fn compile( + &self, + current_context: &CurrentContextWithFns<'_>, + ) -> Result<(), Error> + { + unsafe { + current_context.fns().CompileShader(self.shader); + } + + let mut compile_success = crate::sys::types::GLint::default(); + + unsafe { + current_context.fns().GetShaderiv( + self.shader, + crate::sys::COMPILE_STATUS, + &raw mut compile_success, + ); + } + + if compile_success == 0 { + let info_log = self.get_info_log(current_context); + + return Err(Error::CompileFailed { log: info_log }); + } + + Ok(()) + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().DeleteShader(self.shader); + } + } + + fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String + { + const BUF_SIZE: crate::sys::types::GLsizei = 512; + + let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; + + unsafe { + current_context.fns().GetShaderInfoLog( + self.shader, + BUF_SIZE, + null_mut(), + buf.as_mut_ptr(), + ); + } + + let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; + + unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } + } +} + +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum Kind +{ + Vertex = crate::sys::VERTEX_SHADER, + Fragment = crate::sys::FRAGMENT_SHADER, +} + +/// Shader program +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Program +{ + program: crate::sys::types::GLuint, +} + +impl Program +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let program = unsafe { current_context.fns().CreateProgram() }; + + Self { program } + } + + pub fn attach(&self, current_context: &CurrentContextWithFns<'_>, shader: &Shader) + { + unsafe { + current_context + .fns() + .AttachShader(self.program, shader.shader); + } + } + + /// Links this program. + /// + /// # Errors + /// Returns `Err` if linking fails. + pub fn link(&self, current_context: &CurrentContextWithFns<'_>) -> Result<(), Error> + { + unsafe { + current_context.fns().LinkProgram(self.program); + } + + let mut link_success = crate::sys::types::GLint::default(); + + unsafe { + current_context.fns().GetProgramiv( + self.program, + crate::sys::LINK_STATUS, + &raw mut link_success, + ); + } + + if link_success == 0 { + let info_log = self.get_info_log(current_context); + + return Err(Error::LinkFailed { log: info_log }); + } + + Ok(()) + } + + pub fn activate(&self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().UseProgram(self.program); + } + } + + pub fn set_uniform( + &self, + current_context: &CurrentContextWithFns<'_>, + name: &CStr, + var: &impl UniformVariable, + ) + { + let location = UniformLocation(unsafe { + current_context + .fns() + .GetUniformLocation(self.program, name.as_ptr().cast()) + }); + + var.set(current_context, self, location); + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().DeleteProgram(self.program); + } + } + + fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String + { + const BUF_SIZE: crate::sys::types::GLsizei = 512; + + let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; + + unsafe { + current_context.fns().GetProgramInfoLog( + self.program, + BUF_SIZE, + null_mut(), + buf.as_mut_ptr(), + ); + } + + let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; + + unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } + } +} + +pub trait UniformVariable: ReprC + sealed::Sealed +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ); +} + +impl UniformVariable for f32 +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform1f( + program.program, + uniform_location.0, + *self, + ); + } + } +} + +impl sealed::Sealed for f32 {} + +impl UniformVariable for i32 +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform1i( + program.program, + uniform_location.0, + *self, + ); + } + } +} + +impl sealed::Sealed for i32 {} + +impl UniformVariable for Vec3<f32> +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform3f( + program.program, + uniform_location.0, + self.x, + self.y, + self.z, + ); + } + } +} + +impl sealed::Sealed for Vec3<f32> {} + +impl UniformVariable for Matrix<f32, 4, 4> +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniformMatrix4fv( + program.program, + uniform_location.0, + 1, + crate::sys::FALSE, + self.items.as_ptr().cast::<f32>(), + ); + } + } +} + +impl sealed::Sealed for Matrix<f32, 4, 4> {} + +#[derive(Debug)] +pub struct UniformLocation(crate::sys::types::GLint); + +/// Shader error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("All characters in source are not within the ASCII range")] + SourceNotAscii, + + #[error("Source is too large. Length ({length}) must be < {max_length}")] + SourceTooLarge + { + length: usize, max_length: usize + }, + + #[error("Failed to compile shader")] + CompileFailed + { + log: String + }, + + #[error("Failed to link shader program")] + LinkFailed + { + log: String + }, +} + +mod sealed +{ + pub trait Sealed {} +} diff --git a/opengl-bindings/src/texture.rs b/opengl-bindings/src/texture.rs new file mode 100644 index 0000000..1859beb --- /dev/null +++ b/opengl-bindings/src/texture.rs @@ -0,0 +1,236 @@ +use crate::data_types::Dimens; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Texture +{ + texture: crate::sys::types::GLuint, +} + +impl Texture +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut texture = crate::sys::types::GLuint::default(); + + unsafe { + current_context.fns().CreateTextures( + crate::sys::TEXTURE_2D, + 1, + &raw mut texture, + ); + }; + + Self { texture } + } + + pub fn bind_to_texture_unit( + &self, + current_context: &CurrentContextWithFns<'_>, + texture_unit: u32, + ) + { + unsafe { + current_context + .fns() + .BindTextureUnit(texture_unit, self.texture); + } + } + + /// Allocates the texture storage, stores pixel data & generates a mipmap. + /// + /// # Errors + /// Returns `Err` if any value in `size` does not fit into a `i32`. + pub fn generate( + &self, + current_context: &CurrentContextWithFns<'_>, + size: &Dimens<u32>, + data: &[u8], + pixel_data_format: PixelDataFormat, + ) -> Result<(), GenerateError> + { + let size = Dimens::<crate::sys::types::GLsizei> { + width: size.width.try_into().map_err(|_| { + GenerateError::SizeWidthValueTooLarge { + value: size.width, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + height: size.height.try_into().map_err(|_| { + GenerateError::SizeHeightValueTooLarge { + value: size.height, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + }; + + self.alloc_image(current_context, pixel_data_format, &size, data); + + unsafe { + current_context.fns().GenerateTextureMipmap(self.texture); + } + + Ok(()) + } + + pub fn set_wrap( + &self, + current_context: &CurrentContextWithFns<'_>, + wrapping: Wrapping, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_WRAP_S, + wrapping as i32, + ); + + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_WRAP_T, + wrapping as i32, + ); + } + } + + pub fn set_magnifying_filter( + &self, + current_context: &CurrentContextWithFns<'_>, + filtering: Filtering, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_MAG_FILTER, + filtering as i32, + ); + } + } + + pub fn set_minifying_filter( + &self, + current_context: &CurrentContextWithFns<'_>, + filtering: Filtering, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_MIN_FILTER, + filtering as i32, + ); + } + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context + .fns() + .DeleteTextures(1, &raw const self.texture); + } + } + + fn alloc_image( + &self, + current_context: &CurrentContextWithFns<'_>, + pixel_data_format: PixelDataFormat, + size: &Dimens<crate::sys::types::GLsizei>, + data: &[u8], + ) + { + unsafe { + current_context.fns().TextureStorage2D( + self.texture, + 1, + pixel_data_format.to_sized_internal_format(), + size.width, + size.height, + ); + + current_context.fns().TextureSubImage2D( + self.texture, + 0, + 0, + 0, + size.width, + size.height, + pixel_data_format.to_format(), + crate::sys::UNSIGNED_BYTE, + data.as_ptr().cast(), + ); + } + } +} + +const fn try_cast_u32_to_i32(val: u32) -> i32 +{ + assert!(val <= i32::MAX as u32); + + val.cast_signed() +} + +/// Texture wrapping. +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +pub enum Wrapping +{ + Repeat = const { try_cast_u32_to_i32(crate::sys::REPEAT) }, + MirroredRepeat = const { try_cast_u32_to_i32(crate::sys::MIRRORED_REPEAT) }, + ClampToEdge = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_EDGE) }, + ClampToBorder = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_BORDER) }, +} + +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +pub enum Filtering +{ + Nearest = const { try_cast_u32_to_i32(crate::sys::NEAREST) }, + Linear = const { try_cast_u32_to_i32(crate::sys::LINEAR) }, +} + +/// Texture pixel data format. +#[derive(Debug, Clone, Copy)] +pub enum PixelDataFormat +{ + Rgb8, + Rgba8, +} + +impl PixelDataFormat +{ + fn to_sized_internal_format(self) -> crate::sys::types::GLenum + { + match self { + Self::Rgb8 => crate::sys::RGB8, + Self::Rgba8 => crate::sys::RGBA8, + } + } + + fn to_format(self) -> crate::sys::types::GLenum + { + match self { + Self::Rgb8 => crate::sys::RGB, + Self::Rgba8 => crate::sys::RGBA, + } + } +} + +/// Error generating texture. +#[derive(Debug, thiserror::Error)] +pub enum GenerateError +{ + #[error("Size width value ({value}) is too large. Must be < {max_value}")] + SizeWidthValueTooLarge + { + value: u32, max_value: u32 + }, + #[error("Size height value ({value}) is too large. Must be < {max_value}")] + SizeHeightValueTooLarge + { + value: u32, max_value: u32 + }, +} diff --git a/opengl-bindings/src/vertex_array.rs b/opengl-bindings/src/vertex_array.rs new file mode 100644 index 0000000..9942fe7 --- /dev/null +++ b/opengl-bindings/src/vertex_array.rs @@ -0,0 +1,260 @@ +use std::ffi::{c_int, c_void}; +use std::mem::size_of; + +use safer_ffi::layout::ReprC; + +use crate::buffer::Buffer; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct VertexArray +{ + array: crate::sys::types::GLuint, +} + +impl VertexArray +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut array = 0; + + unsafe { + current_context.fns().CreateVertexArrays(1, &raw mut array); + } + + Self { array } + } + + /// Draws the currently bound vertex array. + /// + /// # Errors + /// Returns `Err` if: + /// - `start_index` is too large + /// - `cnt` is too large + pub fn draw_arrays( + current_context: &CurrentContextWithFns<'_>, + primitive_kind: PrimitiveKind, + start_index: u32, + cnt: u32, + ) -> Result<(), DrawError> + { + let start_index: crate::sys::types::GLint = + start_index + .try_into() + .map_err(|_| DrawError::StartIndexValueTooLarge { + value: start_index, + max_value: crate::sys::types::GLint::MAX as u32, + })?; + + let cnt: crate::sys::types::GLsizei = + cnt.try_into().map_err(|_| DrawError::CountValueTooLarge { + value: cnt, + max_value: crate::sys::types::GLsizei::MAX as u32, + })?; + + unsafe { + current_context + .fns() + .DrawArrays(primitive_kind.into_gl(), start_index, cnt); + } + + Ok(()) + } + + /// Draws the currently bound vertex array. + /// + /// # Errors + /// Returns `Err` if `cnt` is too large. + pub fn draw_elements( + current_context: &CurrentContextWithFns<'_>, + primitive_kind: PrimitiveKind, + start_index: u32, + cnt: u32, + ) -> Result<(), DrawError> + { + let cnt: crate::sys::types::GLsizei = + cnt.try_into().map_err(|_| DrawError::CountValueTooLarge { + value: cnt, + max_value: crate::sys::types::GLsizei::MAX as u32, + })?; + + unsafe { + current_context.fns().DrawElements( + primitive_kind.into_gl(), + cnt, + crate::sys::UNSIGNED_INT, + // TODO: Make this not sometimes UB. DrawElements expects a actual + // pointer to a memory location when no VBO is bound. + // See: https://stackoverflow.com/q/21706113 + std::ptr::without_provenance::<c_void>(start_index as usize), + ); + } + + Ok(()) + } + + pub fn bind_element_buffer( + &self, + current_context: &CurrentContextWithFns<'_>, + element_buffer: &Buffer<u32>, + ) + { + unsafe { + current_context + .fns() + .VertexArrayElementBuffer(self.array, element_buffer.object()); + } + } + + pub fn bind_vertex_buffer<VertexT: ReprC>( + &self, + current_context: &CurrentContextWithFns<'_>, + binding_index: u32, + vertex_buffer: &Buffer<VertexT>, + offset: isize, + ) + { + let vertex_size = const { cast_usize_to_c_int(size_of::<VertexT>()) }; + + unsafe { + current_context.fns().VertexArrayVertexBuffer( + self.array, + binding_index, + vertex_buffer.object(), + offset, + vertex_size, + ); + } + } + + pub fn enable_attrib( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + ) + { + unsafe { + current_context.fns().EnableVertexArrayAttrib( + self.array, + attrib_index as crate::sys::types::GLuint, + ); + } + } + + pub fn set_attrib_format( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + data_type: DataType, + normalized: bool, + offset: u32, + ) + { + unsafe { + current_context.fns().VertexArrayAttribFormat( + self.array, + attrib_index, + data_type.size(), + data_type as u32, + if normalized { + crate::sys::TRUE + } else { + crate::sys::FALSE + }, + offset, + ); + } + } + + /// Associate a vertex attribute and a vertex buffer binding. + pub fn set_attrib_vertex_buf_binding( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + vertex_buf_binding_index: u32, + ) + { + unsafe { + current_context.fns().VertexArrayAttribBinding( + self.array, + attrib_index, + vertex_buf_binding_index, + ); + } + } + + pub fn bind(&self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { current_context.fns().BindVertexArray(self.array) } + } +} + +#[derive(Debug)] +pub enum PrimitiveKind +{ + Triangles, +} + +impl PrimitiveKind +{ + fn into_gl(self) -> crate::sys::types::GLenum + { + match self { + Self::Triangles => crate::sys::TRIANGLES, + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u32)] +pub enum DataType +{ + Float = crate::sys::FLOAT, +} + +impl DataType +{ + fn size(self) -> crate::sys::types::GLint + { + match self { + Self::Float => const { cast_usize_to_c_int(size_of::<f32>()) }, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum DrawError +{ + #[error("Start index value {value} is too large. Must be < {max_value}")] + StartIndexValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Count value {value} is too large. Must be < {max_value}")] + CountValueTooLarge + { + value: u32, max_value: u32 + }, +} + +const fn cast_usize_to_c_int(num: usize) -> c_int +{ + assert!(num <= c_int::MAX.cast_unsigned() as usize); + + c_int::from_ne_bytes(shorten_byte_array(num.to_ne_bytes())) +} + +const fn shorten_byte_array<const SRC_LEN: usize, const DST_LEN: usize>( + src: [u8; SRC_LEN], +) -> [u8; DST_LEN] +{ + assert!(DST_LEN < SRC_LEN); + + let mut ret = [0; DST_LEN]; + + ret.copy_from_slice(src.split_at(DST_LEN).0); + + ret +} |