summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock570
-rw-r--r--Cargo.toml2
-rw-r--r--opengl-bindings/Cargo.toml65
-rw-r--r--opengl-bindings/build.rs107
-rw-r--r--opengl-bindings/src/buffer.rs167
-rw-r--r--opengl-bindings/src/data_types.rs37
-rw-r--r--opengl-bindings/src/debug.rs161
-rw-r--r--opengl-bindings/src/lib.rs119
-rw-r--r--opengl-bindings/src/misc.rs190
-rw-r--r--opengl-bindings/src/shader.rs366
-rw-r--r--opengl-bindings/src/texture.rs236
-rw-r--r--opengl-bindings/src/vertex_array.rs260
12 files changed, 2257 insertions, 23 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4f48d3d..36c48d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
+]
diff --git a/Cargo.toml b/Cargo.toml
index e14b33c..8ba4ecc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
+}