├── .cargo └── config.toml ├── xtask ├── enum_merges.txt ├── enum_overrides.txt ├── Cargo.toml ├── src │ ├── main.rs │ ├── lib.rs │ └── doc_parse.rs └── implemented.txt ├── tests ├── fendevel_moderngl │ ├── .gitignore │ ├── doc │ │ └── screenshot1.jpg │ ├── data │ │ └── textures │ │ │ ├── TC_SkyRed_Xn.png │ │ │ ├── TC_SkyRed_Xp.png │ │ │ ├── TC_SkyRed_Yn.png │ │ │ ├── TC_SkyRed_Yp.png │ │ │ ├── TC_SkyRed_Zn.png │ │ │ ├── TC_SkyRed_Zp.png │ │ │ ├── T_Default_D.png │ │ │ ├── T_Default_E.png │ │ │ ├── T_Default_N.png │ │ │ ├── T_Default_S.png │ │ │ ├── TC_SkySpace_Xn.png │ │ │ ├── TC_SkySpace_Xp.png │ │ │ ├── TC_SkySpace_Yn.png │ │ │ ├── TC_SkySpace_Yp.png │ │ │ ├── TC_SkySpace_Zn.png │ │ │ ├── TC_SkySpace_Zp.png │ │ │ ├── TC_AboveClouds_Xn.png │ │ │ ├── TC_AboveClouds_Xp.png │ │ │ ├── TC_AboveClouds_Yn.png │ │ │ ├── TC_AboveClouds_Yp.png │ │ │ ├── TC_AboveClouds_Zn.png │ │ │ └── TC_AboveClouds_Zp.png │ ├── source │ │ ├── Framebuffer.h │ │ ├── Ssao.h │ │ ├── GLDebug.h │ │ ├── shader │ │ │ ├── blur.vert.glsl │ │ │ ├── blur.frag.glsl │ │ │ ├── gbuffer.vert.glsl │ │ │ ├── main.vert.glsl │ │ │ ├── gbuffer.frag.glsl │ │ │ └── main.frag.glsl │ │ ├── Util.cpp │ │ ├── Framebuffer.cpp │ │ ├── Util.h │ │ ├── Ssao.cpp │ │ ├── GLDebug.cpp │ │ ├── Texture.h │ │ ├── Model.cpp │ │ ├── Shader.cpp │ │ ├── Model.h │ │ ├── Shader.h │ │ └── Texture.cpp │ ├── conanfile.txt │ ├── LICENSE │ ├── Readme.md │ └── CMakeLists.txt └── HelloTriangle │ ├── shader_triangle.frag │ ├── shader_triangle.vert │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── include │ ├── gl_utils.h │ └── KHR │ │ └── khrplatform.h │ ├── hello_colorful_triangle.cpp │ └── gl_utils.cpp ├── .gitignore ├── .vscode ├── settings.json └── c_cpp_properties.json ├── oxidegl ├── src │ ├── commands │ │ ├── mod.rs │ │ ├── clear.rs │ │ └── misc.rs │ ├── gl_types.rs │ ├── context.rs │ ├── lib.rs │ ├── framebuffer.rs │ ├── util.rs │ ├── shader.rs │ ├── error.rs │ ├── conversions.rs │ ├── context │ │ └── state.rs │ └── program.rs ├── Cargo.toml └── build.rs ├── oxidegl_c ├── Cargo.toml └── src │ ├── context.rs │ └── lib.rs ├── .gitmodules ├── oxidegl_shim └── Cargo.toml ├── LICENSE.txt ├── Cargo.toml └── README.md /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /xtask/enum_merges.txt: -------------------------------------------------------------------------------- 1 | SamplerParameterF|SamplerParameterI=>SamplerParameter -------------------------------------------------------------------------------- /tests/fendevel_moderngl/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | CMakeUserPresets.json 4 | .vs/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | generated 3 | 4 | *.DS_Store 5 | *install.sh 6 | /tests/HelloTriangle/bin/* 7 | *.log 8 | *.json.gz -------------------------------------------------------------------------------- /tests/fendevel_moderngl/doc/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/doc/screenshot1.jpg -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Xn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Xn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Xp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Xp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Yn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Yn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Yp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Yp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Zn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Zn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkyRed_Zp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkyRed_Zp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/T_Default_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/T_Default_D.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/T_Default_E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/T_Default_E.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/T_Default_N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/T_Default_N.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/T_Default_S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/T_Default_S.png -------------------------------------------------------------------------------- /tests/HelloTriangle/shader_triangle.frag: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | in vec4 vertexColor; 4 | out vec4 FragColor; 5 | 6 | void main() { 7 | FragColor = vertexColor; 8 | } 9 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Xn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Xn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Xp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Xp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Yn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Yn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Yp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Yp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Zn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Zn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_SkySpace_Zp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_SkySpace_Zp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Xn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Xn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Xp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Xp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Yn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Yn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Yp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Yp.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Zn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Zn.png -------------------------------------------------------------------------------- /tests/fendevel_moderngl/data/textures/TC_AboveClouds_Zp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Random-Scientist/oxidegl/HEAD/tests/fendevel_moderngl/data/textures/TC_AboveClouds_Zp.png -------------------------------------------------------------------------------- /xtask/enum_overrides.txt: -------------------------------------------------------------------------------- 1 | RenderbufferParameterName 2 | TextureCompareMode 3 | TextureWrapMode 4 | TextureMinFilter 5 | TextureMagFilter 6 | TextureWrapMode 7 | TextureSwizzle 8 | ErrorCode -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Framebuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | GLuint create_framebuffer(std::vector const& cols, GLuint depth = GL_NONE); -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Ssao.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::vector generate_ssao_kernel(); 7 | 8 | std::vector generate_ssao_noise(); -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/GLDebug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void APIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./Cargo.toml", 4 | "./Cargo.toml" 5 | ], 6 | "rust-analyzer.checkOnSave": true, 7 | "rust-analyzer.check.command": "clippy", 8 | "editor.formatOnSave": true 9 | } -------------------------------------------------------------------------------- /tests/HelloTriangle/shader_triangle.vert: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout(location = 0) in vec3 aPos; 4 | layout(location = 1) in vec3 aColor; 5 | out vec4 vertexColor; 6 | 7 | void main() { 8 | gl_Position = vec4(aPos, 1.0); 9 | vertexColor = vec4(aColor, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /oxidegl/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod caps; 3 | pub mod clear; 4 | pub mod debug; 5 | pub mod draw; 6 | pub mod get; 7 | pub mod misc; 8 | pub mod programs; 9 | pub mod shaders; 10 | pub mod textures; 11 | #[allow(clippy::missing_safety_doc, clippy::missing_errors_doc)] 12 | pub mod unimplemented; 13 | pub mod vao; 14 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0.86" } 8 | clap = { version = "4.5.7", features = ["derive"] } 9 | const_format = "0.2.32" 10 | dashmap = "6.0.1" 11 | enum_dispatch = "0.3.13" 12 | roxmltree = "0.20.0" 13 | strum_macros = "0.26.3" 14 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | 3 | glad/0.1.36 4 | stb/cci.20220909 5 | glm/cci.20230113 6 | fmtlog/2.2.1 7 | sdl/2.28.2 8 | 9 | [options] 10 | glad/*:gl_profile=core 11 | glad/*:gl_version=4.6 12 | glad/*:spec=gl 13 | glad/*:no_loader=False 14 | 15 | [generators] 16 | CMakeDeps 17 | CMakeToolchain 18 | 19 | [layout] 20 | cmake_layout -------------------------------------------------------------------------------- /oxidegl_c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidegl_c" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | 9 | [dependencies] 10 | flexi_logger = "0.31.2" 11 | 12 | oxidegl = { workspace = true } 13 | objc2 = { workspace = true } 14 | objc2-app-kit = { workspace = true, features = ["std"] } 15 | log = { workspace = true } 16 | likely_stable = { workspace = true } 17 | -------------------------------------------------------------------------------- /tests/HelloTriangle/LICENSE: -------------------------------------------------------------------------------- 1 | PLEASE NOTE 2 | 3 | Code from Anton Gerdelan (gl_utils) is under specific licence : 4 | https://github.com/capnramses/antons_opengl_tutorials_book/blob/master/LICENCE.md 5 | 6 | Code from learnOpenGL.com is under Creative Commons: 7 | https://github.com/JoeyDeVries/LearnOpenGL/blob/master/LICENSE.md 8 | 9 | Glad lib is free to use for any purpose. 10 | 11 | My code is public domain. 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "oxidegl-glfw"] 2 | path = oxidegl-glfw 3 | url = https://github.com/The-Minecraft-Scientist/oxidegl-glfw.git 4 | [submodule "reference/OpenGL-Refpages"] 5 | path = reference/OpenGL-Refpages 6 | url = https://github.com/The-Minecraft-Scientist/OpenGL-Refpages 7 | [submodule "reference/OpenGL-Registry"] 8 | path = reference/OpenGL-Registry 9 | url = https://github.com/The-Minecraft-Scientist/OpenGL-Registry 10 | -------------------------------------------------------------------------------- /oxidegl_shim/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidegl_shim" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | objc2 = { workspace = true } 11 | objc2-app-kit = { workspace = true } 12 | objc2-foundation = { workspace = true } 13 | log = { workspace = true } 14 | oxidegl_c = { workspace = true } 15 | oxidegl = { workspace = true } 16 | ctor = "0.5.0" 17 | core-foundation-sys = "0.8.7" 18 | libc = "0.2.158" 19 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env::set_current_dir, path::PathBuf}; 2 | 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use xtask::tasks::TaskTrait; 6 | 7 | fn main() -> Result<()> { 8 | let args = Args::parse(); 9 | // Parent directory of xtask manifest dir is workspace root 10 | let xtask_dir = env!("CARGO_MANIFEST_DIR"); 11 | set_current_dir(PathBuf::from(xtask_dir).parent().unwrap()).unwrap(); 12 | println!("{:#?}", &args); 13 | args.command.execute() 14 | } 15 | #[derive(Parser, Debug)] 16 | #[command(version, about, long_about = None)] 17 | struct Args { 18 | /// Subcommand to run 19 | #[command(subcommand)] 20 | command: xtask::tasks::Task, 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | OxideGL 2 | Copyright (C) 2024 RandomScientist 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "macFrameworkPath": [ 10 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" 11 | ], 12 | "compilerPath": "/usr/bin/clang", 13 | "cStandard": "c17", 14 | "cppStandard": "c++17", 15 | "intelliSenseMode": "macos-clang-x64", 16 | "compileCommands": "${workspaceFolder}/oxidegl-glfw/compile_commands.json" 17 | } 18 | ], 19 | "version": 4 20 | } -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/blur.vert.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | out gl_PerVertex{ vec4 gl_Position; }; 5 | 6 | layout(location = 3) uniform vec2 u_uv_diff; 7 | 8 | out out_block 9 | { 10 | vec2 texcoord; 11 | } o; 12 | 13 | void main() 14 | { 15 | vec2 v[] = { 16 | vec2(-1.0f, 1.0f), 17 | vec2(1.0f, 1.0f), 18 | vec2(1.0f,-1.0f), 19 | vec2(-1.0f,-1.0f) 20 | }; 21 | vec2 t[] = { 22 | vec2(0.0f, 1.0f), 23 | vec2(1.0f, 1.0f), 24 | vec2(1.0f, 0.0f), 25 | vec2(0.0f, 0.0f) 26 | 27 | }; 28 | uint i[] = { 0, 3, 2, 2, 1, 0 }; 29 | 30 | const vec2 position = v[i[gl_VertexID]]; 31 | const vec2 texcoord = t[i[gl_VertexID]]; 32 | 33 | o.texcoord = texcoord * u_uv_diff; 34 | gl_Position = vec4(position, 0.0, 1.0); 35 | } 36 | 37 | )===" -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["oxidegl", "oxidegl_c", "oxidegl_shim", "xtask"] 4 | 5 | [profile.release] 6 | codegen-units = 1 7 | lto = "thin" 8 | panic = "abort" 9 | debug = true 10 | 11 | [profile.dev] 12 | debug = "line-tables-only" 13 | codegen-units = 512 14 | # lto = "thin" 15 | panic = "abort" 16 | incremental = true 17 | 18 | [workspace.dependencies] 19 | 20 | oxidegl = { path = "oxidegl" } 21 | oxidegl_c = { path = "oxidegl_c" } 22 | 23 | strum_macros = "0.27.1" 24 | objc2-app-kit = { default-features = false, version = "0.3.1" } 25 | objc2 = "0.6.1" 26 | log = "0.4.21" 27 | likely_stable = "0.1.2" 28 | 29 | objc2-foundation = { default-features = false, version = "0.3.0", features = [ 30 | "std", 31 | "NSProcessInfo", 32 | ] } 33 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | std::string read_text_file(std::string_view filepath) 10 | { 11 | if (!std::filesystem::exists(filepath.data())) 12 | { 13 | std::ostringstream message; 14 | message << "file " << filepath.data() << " does not exist."; 15 | throw std::filesystem::filesystem_error(message.str(), std::make_error_code(std::errc::no_such_file_or_directory)); 16 | } 17 | std::ifstream file(filepath.data()); 18 | return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); 19 | } 20 | 21 | glm::vec3 orbit_axis(float angle, glm::vec3 const& axis, glm::vec3 const& spread) { return glm::angleAxis(angle, axis) * spread; } 22 | 23 | float lerp(float a, float b, float f) { return a + f * (b - a); } -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Framebuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "Framebuffer.h" 2 | 3 | #include 4 | #include 5 | 6 | GLuint create_framebuffer(std::vector const& cols, GLuint depth) 7 | { 8 | GLuint fbo = 0; 9 | glCreateFramebuffers(1, &fbo); 10 | 11 | for (auto i = 0; i < cols.size(); i++) 12 | { 13 | glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0 + i, cols[i], 0); 14 | } 15 | 16 | std::array draw_buffs; 17 | for (GLenum i = 0; i < cols.size(); i++) 18 | { 19 | draw_buffs[i] = GL_COLOR_ATTACHMENT0 + i; 20 | } 21 | 22 | glNamedFramebufferDrawBuffers(fbo, cols.size(), draw_buffs.data()); 23 | 24 | if (depth != GL_NONE) 25 | { 26 | glNamedFramebufferTexture(fbo, GL_DEPTH_ATTACHMENT, depth, 0); 27 | } 28 | 29 | if (glCheckNamedFramebufferStatus(fbo, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) 30 | { 31 | throw std::runtime_error("incomplete framebuffer"); 32 | } 33 | return fbo; 34 | } 35 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/blur.frag.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | layout (location = 0) out vec4 col; 5 | layout (binding = 0) uniform sampler2D tex_col; 6 | layout (binding = 1) uniform sampler2D tex_vel; 7 | 8 | layout (location = 0) uniform float vel_scale; 9 | 10 | in in_block 11 | { 12 | vec2 texcoord; 13 | } inp; 14 | 15 | void main() 16 | { 17 | vec2 texel_size = 1.0 / vec2(textureSize(tex_col, 0)); 18 | vec2 tex_coords = gl_FragCoord.xy * texel_size; 19 | vec2 vel = texture(tex_vel, tex_coords).rg; 20 | vel *= vel_scale; 21 | 22 | float speed = length(vel / texel_size); 23 | int samples = clamp(int(speed), 1, 40); 24 | 25 | col = texture(tex_col, inp.texcoord); 26 | 27 | for (int i = 1; i < samples; ++i) 28 | { 29 | vec2 offset = vel * (float(i) / float(samples - 1) - 0.5); 30 | col += texture(tex_col, tex_coords + offset); 31 | } 32 | col /= float(samples); 33 | } 34 | 35 | )===" -------------------------------------------------------------------------------- /oxidegl/src/gl_types.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | pub type GLint = i32; 4 | pub type GLuint64 = u64; 5 | pub type GLuint = u32; 6 | pub type GLfloat = f32; 7 | pub type GLboolean = bool; 8 | pub type GLchar = std::ffi::c_char; 9 | pub type GLdouble = f64; 10 | pub type GLsizeiptr = isize; 11 | pub type GLushort = u16; 12 | pub type GLsizei = i32; 13 | pub type GLintptr = isize; 14 | pub type GLenum = u32; 15 | pub type GLbitfield = u32; 16 | pub type GLshort = i16; 17 | pub type GLubyte = u8; 18 | pub type GLDEBUGPROC = Option< 19 | unsafe extern "C" fn( 20 | source: GLenum, 21 | typ: GLenum, 22 | id: GLuint, 23 | severity: GLenum, 24 | length: GLsizei, 25 | message: *const GLchar, 26 | userParam: *const c_void, 27 | ), 28 | >; 29 | pub type GLbyte = i8; 30 | pub type GLsync = 31 | Option; 32 | 33 | pub type GLint64 = i64; 34 | pub type GLvoid = c_void; 35 | -------------------------------------------------------------------------------- /tests/HelloTriangle/Makefile: -------------------------------------------------------------------------------- 1 | # Hellor Colorful Triangle Makefile 2 | # ludo456 @github 3 | # 4 | # -g adds debugging information to the executable file 5 | # -Wall turns on most, but not all, compiler warnings 6 | # 7 | 8 | CC = g++ 9 | CFLAGS = -g -Wall -Wl,-rpath,"@executable_path/../../../oxidegl-glfw/debug/src/" 10 | LIBS = -L"../../oxidegl-glfw/debug/src/" -ldl -lglfw 11 | INCL = ./include 12 | DEPS = gl_utils.cpp glad.c 13 | EXE = hello_colorful_triangle 14 | 15 | .PHONY: run 16 | 17 | # typing 'make' will invoke the first target entry in the file 18 | # (in this case the default target entry) 19 | # you can name this target entry anything, but "default" or "all" 20 | # are the most commonly used names by convention 21 | # 22 | all: $(EXE) run 23 | 24 | # To create the executable file count we need the object files 25 | # countwords.o, counter.o, and scanner.o: 26 | # 27 | $(EXE): $(EXE).cpp $(DEPS) 28 | $(CC) $(CFLAGS) $^ $(LIBS) -I$(INCL) -o bin/$(EXE) -v 29 | 30 | run: 31 | ./bin/$(EXE) 32 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | std::string read_text_file(std::string_view filepath); 12 | 13 | template 14 | std::string string_format(const std::string& format, Args ... args) 15 | { 16 | const size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0' 17 | std::unique_ptr buf(new char[size]); 18 | snprintf(buf.get(), size, format.c_str(), args ...); 19 | return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside 20 | } 21 | 22 | template 23 | int64_t now() 24 | { 25 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); 26 | } 27 | 28 | glm::vec3 orbit_axis(float angle, glm::vec3 const& axis, glm::vec3 const& spread); 29 | 30 | float lerp(float a, float b, float f); 31 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Ssao.cpp: -------------------------------------------------------------------------------- 1 | #include "Ssao.h" 2 | 3 | #include 4 | 5 | #include "Util.h" 6 | 7 | std::vector generate_ssao_kernel() 8 | { 9 | std::vector res; 10 | res.reserve(64); 11 | 12 | std::uniform_real_distribution random(0.0f, 1.0f); 13 | std::default_random_engine generator; 14 | 15 | for (int i = 0; i < 64; ++i) 16 | { 17 | auto sample = glm::normalize(glm::vec3( 18 | random(generator) * 2.0f - 1.0f, 19 | random(generator) * 2.0f - 1.0f, 20 | random(generator) 21 | )) * random(generator) 22 | * lerp(0.1f, 1.0f, glm::pow(float(i) / 64.0f, 2)); 23 | res.push_back(sample); 24 | } 25 | return res; 26 | } 27 | 28 | std::vector generate_ssao_noise() 29 | { 30 | std::vector res; 31 | res.reserve(16); 32 | 33 | std::uniform_real_distribution random(0.0f, 1.0f); 34 | std::default_random_engine generator; 35 | 36 | for (int i = 0; i < 16; ++i) 37 | res.push_back(glm::vec3( 38 | random(generator) * 2.0f - 1.0f, 39 | random(generator) * 2.0f - 1.0f, 40 | 0.0f 41 | )); 42 | 43 | return res; 44 | } -------------------------------------------------------------------------------- /tests/fendevel_moderngl/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 fennec-kun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/GLDebug.cpp: -------------------------------------------------------------------------------- 1 | #include "GLDebug.h" 2 | 3 | #include 4 | #include 5 | 6 | void APIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) 7 | { 8 | std::ostringstream str; 9 | str << "message: " << message << '\n'; 10 | str << "type: "; 11 | switch (type) 12 | { 13 | case GL_DEBUG_TYPE_ERROR: str << "ERROR"; break; 14 | case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: str << "DEPRECATED_BEHAVIOR"; break; 15 | case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: str << "UNDEFINED_BEHAVIOR"; break; 16 | case GL_DEBUG_TYPE_PORTABILITY: str << "PORTABILITY"; break; 17 | case GL_DEBUG_TYPE_PERFORMANCE: str << "PERFORMANCE"; break; 18 | case GL_DEBUG_TYPE_OTHER: str << "OTHER"; break; 19 | } 20 | 21 | str << '\n'; 22 | str << "id: " << id << '\n'; 23 | str << "severity: "; 24 | switch (severity) 25 | { 26 | case GL_DEBUG_SEVERITY_LOW: str << "LOW"; break; 27 | case GL_DEBUG_SEVERITY_MEDIUM: str << "MEDIUM"; break; 28 | case GL_DEBUG_SEVERITY_HIGH: str << "HIGH"; break; 29 | default: str << "NONE"; 30 | } 31 | 32 | std::clog << str.str() << std::endl; 33 | } -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/gbuffer.vert.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | out gl_PerVertex { vec4 gl_Position; }; 5 | 6 | out out_block 7 | { 8 | vec3 pos; 9 | vec3 nrm; 10 | vec2 uvs; 11 | smooth vec4 curr_pos; 12 | smooth vec4 prev_pos; 13 | } o; 14 | 15 | layout (location = 0) in vec3 pos; 16 | layout (location = 1) in vec3 col; 17 | layout (location = 2) in vec3 nrm; 18 | layout (location = 3) in vec2 uvs; 19 | 20 | layout (location = 0) uniform mat4 proj; 21 | layout (location = 1) uniform mat4 view; 22 | layout (location = 2) uniform mat4 modl; 23 | layout (location = 3) uniform mat4 mvp_curr; 24 | layout (location = 4) uniform mat4 mvp_prev; 25 | layout (location = 5) uniform bool except; 26 | 27 | void main() 28 | { 29 | if (!except) 30 | { 31 | o.curr_pos = mvp_curr * vec4(pos, 1.0); 32 | o.prev_pos = mvp_prev * vec4(pos, 1.0); 33 | } 34 | else 35 | { 36 | o.curr_pos = mvp_curr * vec4(pos, 1.0); 37 | o.prev_pos = o.curr_pos; 38 | } 39 | const vec4 mpos = (view * modl * vec4(pos, 1.0)); 40 | o.pos = (modl * vec4(pos, 1.0)).xyz; 41 | o.nrm = mat3(transpose(inverse(modl))) * nrm; 42 | o.uvs = uvs; 43 | gl_Position = proj * mpos; 44 | } 45 | 46 | )===" -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/main.vert.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | out gl_PerVertex{ vec4 gl_Position; }; 5 | 6 | layout(location = 0) uniform mat3 u_camera_direction; 7 | layout(location = 1) uniform float u_fov; 8 | layout(location = 2) uniform float u_ratio; 9 | layout(location = 3) uniform vec2 u_uv_diff; 10 | 11 | out out_block 12 | { 13 | vec2 texcoord; 14 | vec3 ray; 15 | } o; 16 | 17 | vec3 skyray(vec2 texcoord, float fovy, float aspect) 18 | { 19 | float d = 0.5 / tan(fovy / 2.0); 20 | return normalize(vec3((texcoord.x - 0.5) * aspect, texcoord.y - 0.5, -d)); 21 | } 22 | 23 | void main() 24 | { 25 | vec2 v[] = { 26 | vec2(-1.0f, 1.0f), 27 | vec2(1.0f, 1.0f), 28 | vec2(1.0f,-1.0f), 29 | vec2(-1.0f,-1.0f) 30 | }; 31 | vec2 t[] = { 32 | vec2(0.0f, 1.0f), 33 | vec2(1.0f, 1.0f), 34 | vec2(1.0f, 0.0f), 35 | vec2(0.0f, 0.0f) 36 | 37 | }; 38 | uint i[] = { 0, 3, 2, 2, 1, 0 }; 39 | 40 | const vec2 position = v[i[gl_VertexID]]; 41 | const vec2 texcoord = t[i[gl_VertexID]]; 42 | 43 | o.ray = u_camera_direction * skyray(texcoord, u_fov, u_ratio); 44 | o.texcoord = texcoord * u_uv_diff; 45 | gl_Position = vec4(position, 0.0, 1.0); 46 | } 47 | 48 | )===" -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/gbuffer.frag.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | in in_block 5 | { 6 | vec3 pos; 7 | vec3 nrm; 8 | vec2 uvs; 9 | smooth vec4 curr_pos; 10 | smooth vec4 prev_pos; 11 | } i; 12 | 13 | layout(location = 0) out vec3 out_pos; 14 | layout(location = 1) out vec3 out_nrm; 15 | layout(location = 2) out vec4 out_alb; 16 | layout(location = 3) out vec2 out_vel; 17 | layout(location = 4) out vec3 out_emi; 18 | 19 | layout(binding = 0) uniform sampler2D dif; 20 | layout(binding = 1) uniform sampler2D spc; 21 | layout(binding = 2) uniform sampler2D nrm; 22 | layout(binding = 3) uniform sampler2D emi; 23 | 24 | layout(location = 0) uniform float emissive_strength; 25 | 26 | void main() 27 | { 28 | vec3 dif_tex = texture(dif, i.uvs).rgb; 29 | vec3 spc_tex = texture(spc, i.uvs).rgb; 30 | vec3 nrm_tex = texture(nrm, i.uvs).rgb; 31 | 32 | out_pos = i.pos; 33 | out_nrm = normalize(cross(i.nrm, nrm_tex)); 34 | out_alb.rgb = texture(dif, i.uvs).rgb; 35 | out_alb.a = texture(spc, i.uvs).r; 36 | out_vel = ((i.curr_pos.xy / i.curr_pos.w) * 0.5 + 0.5) - ((i.prev_pos.xy / i.prev_pos.w) * 0.5 + 0.5); 37 | out_emi = texture(emi, i.uvs).rgb * emissive_strength; 38 | } 39 | 40 | )===" -------------------------------------------------------------------------------- /tests/HelloTriangle/README.md: -------------------------------------------------------------------------------- 1 | # OpenGL-4.6-Hello-Triangle 2 | 3 | The famous OpenGL "Hello triangle" using shaders. It uses the OpenGL 4.5 functionality called [Direct State Access](https://www.khronos.org/opengl/wiki/Direct_State_Access). Be aware, not-that-old hardware may not be compatible with this functionality. 4 | 5 | As of November 2020, it is the most up-to-date "hello triangle" example I could piece together. 6 | 7 | ## Build and run 8 | 9 | I work on GNU/Linux Ubuntu 20. Obviously you need to install a compiler (g++), make, opengl and glfw3. 10 | 11 | `sudo apt install g++ build-essential libopengl0 libopengl-dev libglfw3 libglfw3-dev` 12 | 13 | Run `make` in the directory of the project. 14 | 15 | ## Glad 16 | 17 | This code uses Glad as its openGL extensions loader. Glew would work fine too. 18 | The directories include/glad, include/KHR, and the files glad.h, glad.c and khrplatform.h were generated from [Glad website](https://glad.dav1d.de/) 19 | 20 | ## Greetings 21 | 22 | I copied/modified code from [Anton Gerdelan](https://antongerdelan.net/opengl/index.html) and [Joey De Vries / Learnopengl](https://learnopengl.com/Getting-started/Shaders). See Licence file for more info. Also check [Fendevel repos about modern OpenGL](https://github.com/fendevel). 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using stb_comp_t = int; 11 | GLuint create_texture_2d_from_file(std::string_view filepath, stb_comp_t comp = STBI_rgb_alpha, bool generateMipMaps = false); 12 | 13 | GLuint create_texture_2d(GLenum internal_format, GLenum format, GLsizei width, GLsizei height, void* data = nullptr, GLenum filter = GL_LINEAR, GLenum repeat = GL_REPEAT, bool generateMipMaps = false); 14 | 15 | GLuint create_texture_cube_from_file(std::array const& filepath, stb_comp_t comp = STBI_rgb_alpha); 16 | 17 | template 18 | GLuint create_texture_cube(GLenum internal_format, GLenum format, GLsizei width, GLsizei height, std::array const& data) 19 | { 20 | GLuint tex = 0; 21 | glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &tex); 22 | glTextureStorage2D(tex, 1, internal_format, width, height); 23 | for (GLint i = 0; i < 6; ++i) 24 | { 25 | if (data[i]) 26 | { 27 | glTextureSubImage3D(tex, 0, 0, 0, i, width, height, 1, format, GL_UNSIGNED_BYTE, data[i]); 28 | } 29 | } 30 | // Fix cubemap seams 31 | glTextureParameteri(tex, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 32 | glTextureParameteri(tex, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 33 | 34 | return tex; 35 | } -------------------------------------------------------------------------------- /oxidegl/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{gl_enums::ErrorCode, render::Renderer}; 2 | use objc2::rc::Retained; 3 | use objc2_app_kit::NSView; 4 | use objc2_metal::MTLPixelFormat; 5 | use state::GlState; 6 | 7 | pub(crate) mod state; 8 | #[derive(Debug)] 9 | #[repr(C)] 10 | pub struct Context { 11 | pub(crate) gl_state: GlState, 12 | pub(crate) renderer: Renderer, 13 | } 14 | 15 | impl Context { 16 | #[must_use] 17 | pub fn new() -> Self { 18 | Self { 19 | gl_state: GlState::default(), 20 | renderer: Renderer::new(MTLPixelFormat::BGRA8Unorm_sRGB, None, None), 21 | } 22 | } 23 | pub fn set_error(&mut self, error: ErrorCode) { 24 | self.gl_state.error = error; 25 | } 26 | pub fn set_view(&mut self, view: &Retained) { 27 | let backing_scale_factor = view.window().map_or(1.0, |w| w.backingScaleFactor()); 28 | self.renderer.set_view(view, backing_scale_factor); 29 | // init scissor box/viewport now that we have an actual view 30 | let dims = self.renderer.target_defaultfb_dims(); 31 | self.gl_state.viewport.width = dims.0; 32 | self.gl_state.viewport.height = dims.1; 33 | 34 | self.gl_state.scissor_box.width = dims.0; 35 | self.gl_state.scissor_box.height = dims.1; 36 | } 37 | } 38 | 39 | impl Default for Context { 40 | fn default() -> Self { 41 | Self::new() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/Readme.md: -------------------------------------------------------------------------------- 1 | # Modern OpenGL - Deferred Rending 2 | 3 | ![Screenshot 1](./doc/screenshot1.jpg) 4 | 5 | Modern OpenGL deferred rendering example 6 | 7 | This little project shall contain the tools to produce the following techniques and effects: 8 | * deferred rendering 9 | * deferred lighting 10 | * shadows 11 | * bloom 12 | 13 | ## Requirements 14 | 15 | Development is done with 16 | * Conan 2.x 17 | * CMake 3.27.x 18 | * Visual Studio 2022 Community Edition 19 | 20 | ## Building 21 | 22 | Conan install for debug and release build types 23 | ``` 24 | conan install . --build=missing --settings=build_type=Debug && conan install . --build=missing --settings=build_type=Release 25 | ``` 26 | 27 | CMake project generation 28 | ``` 29 | cmake --preset conan-default 30 | ``` 31 | 32 | For Windows development, open the solution file (.sln) located in the newly created build directory. 33 | For Linux development, note that the build directory now should contain a Debug and Release directory which can be build via build command separately. 34 | 35 | ## Troubleshooting 36 | 37 | In case of issues try (in no particular order) 38 | * Removing the build directory and the CMakeUserPresets.json 39 | * Cleaning the repository via ```git clean -xfd``` 40 | * Deleting and cloning the whole repository again 41 | * Ensure your cmake version is at least 3.27.0 42 | * Ensure your conan profile is generated in case of a new conan installation 43 | * Ensure your conan is at version 2.x 44 | * Ensure your conan profile is set to C++ standard 17 or higher -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Model.cpp: -------------------------------------------------------------------------------- 1 | #include "Model.h" 2 | 3 | std::vector calc_tangents(std::vector const& vertices, std::vector const& indices) 4 | { 5 | std::vector res; 6 | res.reserve(indices.size()); 7 | for (auto q = 0; q < 6; ++q) 8 | { 9 | auto 10 | v = q * 4, 11 | i = q * 6; 12 | glm::vec3 13 | edge0 = vertices[indices[i + 1]].position - vertices[indices[i + 0]].position, edge1 = vertices[indices[i + 2]].position - vertices[indices[0]].position, 14 | edge2 = vertices[indices[i + 4]].position - vertices[indices[i + 3]].position, edge3 = vertices[indices[i + 5]].position - vertices[indices[3]].position; 15 | 16 | glm::vec2 17 | delta_uv0 = vertices[indices[i + 1]].texcoord - vertices[indices[i + 0]].texcoord, delta_uv1 = vertices[indices[i + 2]].texcoord - vertices[indices[i + 0]].texcoord, 18 | delta_uv2 = vertices[indices[i + 4]].texcoord - vertices[indices[i + 3]].texcoord, delta_uv3 = vertices[indices[i + 5]].texcoord - vertices[indices[i + 3]].texcoord; 19 | 20 | float const 21 | f0 = 1.0f / (delta_uv0.x * delta_uv1.y - delta_uv1.x * delta_uv0.y), 22 | f1 = 1.0f / (delta_uv2.x * delta_uv3.y - delta_uv3.x * delta_uv2.y); 23 | 24 | auto const 25 | t0 = glm::normalize(glm::vec3( 26 | f0 * (delta_uv1.y * edge0.x - delta_uv0.y * edge1.x), 27 | f0 * (delta_uv1.y * edge0.y - delta_uv0.y * edge1.y), 28 | f0 * (delta_uv1.y * edge0.z - delta_uv0.y * edge1.z) 29 | )), 30 | t1 = glm::normalize(glm::vec3( 31 | f1 * (delta_uv3.y * edge2.x - delta_uv2.y * edge3.x), 32 | f1 * (delta_uv3.y * edge2.y - delta_uv2.y * edge3.y), 33 | f1 * (delta_uv3.y * edge2.z - delta_uv2.y * edge3.z) 34 | )); 35 | 36 | res.push_back(t0); res.push_back(t0); res.push_back(t0); 37 | res.push_back(t1); res.push_back(t1); res.push_back(t1); 38 | } 39 | return res; 40 | } -------------------------------------------------------------------------------- /tests/HelloTriangle/include/gl_utils.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | | OpenGL 4 Example Code. | 3 | | Accompanies written series "Anton's OpenGL 4 Tutorials" | 4 | | Email: anton at antongerdelan dot net | 5 | | First version 27 Jan 2014 | 6 | | Dr Anton Gerdelan, Trinity College Dublin, Ireland. | 7 | | See individual libraries for separate legal notices | 8 | \******************************************************************************/ 9 | #ifndef _GL_UTILS_H_ 10 | #define _GL_UTILS_H_ 11 | 12 | #include "glad/glad.h" // need to include glad first so it defines _gl_h_ and GLFW doesn't screw us over by including NSGL headers 13 | 14 | #include "../../../oxidegl-glfw/include/GLFW/glfw3.h" // GLFW helper library 15 | #include 16 | 17 | #define GL_LOG_FILE "gl.log" 18 | 19 | extern GLFWwindow *g_window; 20 | 21 | bool restart_gl_log(); 22 | 23 | bool gl_log(const char *message, ...); 24 | 25 | /* same as gl_log except also prints to stderr */ 26 | bool gl_log_err(const char *message, ...); 27 | 28 | void glfw_error_callback(int error, const char *description); 29 | 30 | void log_gl_params(); 31 | 32 | void _update_fps_counter(GLFWwindow *window); 33 | 34 | const char *GL_type_to_string(unsigned int type); 35 | 36 | void print_shader_info_log(GLuint shader_index); 37 | 38 | void print_programme_info_log(GLuint sp); 39 | 40 | void print_all(GLuint sp); 41 | 42 | bool is_valid(GLuint sp); 43 | 44 | bool parse_file_into_str(const char *file_name, char *shader_str, int max_len); 45 | 46 | GLuint create_programme_from_files(const char *vert_file_name, 47 | const char *frag_file_name); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24.0) 2 | project(DeferredRender) 3 | 4 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 5 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 6 | set(CXX_STANDARD_REQUIRED ON) 7 | 8 | if (MSVC) 9 | # Ignore warnings about missing pdb 10 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4099") 11 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /ignore:4099") 12 | set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4099") 13 | endif() 14 | 15 | find_package(OpenGL REQUIRED) 16 | find_package(glad REQUIRED) 17 | find_package(stb REQUIRED) 18 | find_package(glm REQUIRED) 19 | find_package(fmtlog REQUIRED) 20 | find_package(SDL2 REQUIRED) 21 | 22 | # App 23 | file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS 24 | ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp 25 | ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cxx 26 | ${CMAKE_CURRENT_SOURCE_DIR}/source/*.h 27 | ${CMAKE_CURRENT_SOURCE_DIR}/source/*.hpp 28 | ${CMAKE_CURRENT_SOURCE_DIR}/source/*.glsl 29 | ) 30 | 31 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES}) 32 | 33 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 34 | 35 | set_target_properties(${PROJECT_NAME} PROPERTIES 36 | CXX_STANDARD 17 37 | ) 38 | 39 | target_link_libraries(${PROJECT_NAME} 40 | PUBLIC glad::glad 41 | PUBLIC OpenGL::GL 42 | PUBLIC stb::stb 43 | PUBLIC fmtlog::fmtlog 44 | PUBLIC glm::glm 45 | PUBLIC SDL2::SDL2 46 | ) 47 | 48 | target_include_directories(${PROJECT_NAME} 49 | PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/source 50 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include 51 | ) 52 | 53 | target_compile_options(${PROJECT_NAME} PRIVATE 54 | $<$:/WX /W2> 55 | # $<$>:-Wall -Wextra -Wpedantic -Werror> 56 | ) 57 | 58 | file (COPY ${CMAKE_CURRENT_SOURCE_DIR}/data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -------------------------------------------------------------------------------- /xtask/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufWriter, Write}, 4 | path::Path, 5 | }; 6 | 7 | use anyhow::Result; 8 | use roxmltree::{Attribute, Node}; 9 | pub mod codegen; 10 | pub mod doc_parse; 11 | pub mod tasks; 12 | 13 | pub fn remove_multi(s: &str, m: &[&str]) -> String { 14 | let mut out = String::with_capacity(s.len()); 15 | for char in s.chars() { 16 | out.push(char); 17 | for filt in m { 18 | if out.ends_with(filt) { 19 | out.truncate(out.len() - filt.len()); 20 | } 21 | } 22 | } 23 | out 24 | } 25 | 26 | trait NodeExt: Sized { 27 | fn find_named_child(&self, name: &str) -> Option; 28 | fn find_named_attribute<'a>(&'a self, name: &'a str) -> Option>; 29 | } 30 | impl NodeExt for Node<'_, '_> { 31 | fn find_named_child(&self, name: &str) -> Option { 32 | self.children() 33 | .find(|child| child.tag_name().name() == name) 34 | } 35 | 36 | fn find_named_attribute<'b>(&'b self, name: &'b str) -> Option> { 37 | let mut attrs = self.attributes(); 38 | attrs.find(|attr| attr.name() == name) 39 | } 40 | } 41 | fn snake_case_from_title_case(src: &str) -> String { 42 | let mut out = String::new(); 43 | let mut prev: Option = None; 44 | for c in src.chars() { 45 | match prev { 46 | Some(prev) => { 47 | if !prev.is_numeric() && c.is_numeric() || c.is_uppercase() && !prev.is_numeric() { 48 | out.push('_'); 49 | } 50 | out.extend(c.to_lowercase()); 51 | } 52 | None => out.extend(c.to_lowercase()), 53 | } 54 | prev = Some(c); 55 | } 56 | out 57 | } 58 | fn open_file_writer>(path: P) -> Result { 59 | let _ = std::fs::remove_file(&path); 60 | let file = File::create(path)?; 61 | Ok(BufWriter::new(file)) 62 | } 63 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/shader/main.frag.glsl: -------------------------------------------------------------------------------- 1 | R"===( 2 | #version 460 3 | 4 | layout (location = 0) out vec3 col; 5 | 6 | // Textures 7 | layout(binding = 0) uniform sampler2D tex_position; 8 | layout(binding = 1) uniform sampler2D tex_normal; 9 | layout(binding = 2) uniform sampler2D tex_albedo; 10 | layout(binding = 3) uniform sampler2D tex_depth; 11 | layout(binding = 4) uniform sampler2D tex_emissive; 12 | layout(binding = 5) uniform samplerCube texcube_skybox; 13 | 14 | layout(location = 0) uniform vec3 u_camera_position; 15 | // Point light 16 | layout(location = 1) uniform vec3 light_col; 17 | layout(location = 2) uniform vec3 light_pos; 18 | 19 | in in_block 20 | { 21 | vec2 texcoord; 22 | vec3 ray; 23 | } i; 24 | 25 | vec3 calculate_specular(float strength, vec3 color, vec3 view_pos, vec3 vert_pos, vec3 light_dir, vec3 normal) 26 | { 27 | vec3 view_dir = normalize(view_pos - vert_pos); 28 | vec3 ref_dir = reflect(-light_dir, normal); 29 | 30 | float spec = pow(max(dot(view_dir, ref_dir), 0.0), 32.0); 31 | return strength * spec * color; 32 | } 33 | 34 | void main() 35 | { 36 | const float depth = texture(tex_depth, i.texcoord).r; 37 | if (depth == 1.0) 38 | { 39 | col = texture(texcube_skybox, i.ray).rgb; 40 | return; 41 | } 42 | 43 | const vec3 position = texture(tex_position, i.texcoord).rgb; 44 | const vec3 normal = texture(tex_normal, i.texcoord).rgb; 45 | // Albedo = diffuse 46 | const vec4 albedo_specular = texture(tex_albedo, i.texcoord); 47 | const vec3 albedo = albedo_specular.rgb; 48 | const float specular = albedo_specular.a; 49 | 50 | vec3 light_dir = normalize(light_pos - position); 51 | float light_dif = max(dot(normal, light_dir), 0.0); 52 | 53 | 54 | vec3 ambient_col = vec3(0.1); 55 | 56 | // Final colors 57 | vec3 ambient = ambient_col * albedo; 58 | vec3 diffuse = light_col * light_dif * albedo; 59 | vec3 light_spec = calculate_specular(specular, light_col, u_camera_position, position, light_dir, normal); 60 | vec3 emission = texture(tex_emissive, i.texcoord).rgb; 61 | 62 | col = ambient + diffuse + light_spec + emission; 63 | col = col / (col + vec3(1.0)); 64 | } 65 | 66 | )===" -------------------------------------------------------------------------------- /oxidegl_c/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::Cell, pin::Pin, ptr::NonNull}; 2 | 3 | use likely_stable::if_likely; 4 | use log::trace; 5 | use oxidegl::{ 6 | context::Context, 7 | error::{GetErrorReturnValue, GlResult}, 8 | gl_enums::ErrorCode, 9 | }; 10 | 11 | thread_local! { 12 | pub static CTX: Cell>> = const { Cell::new(None) }; 13 | } 14 | #[inline] 15 | #[cfg_attr(debug_assertions, track_caller)] 16 | #[expect(unused_mut, unused_variables, reason = "lint bug")] 17 | pub fn with_ctx_mut< 18 | Ret, 19 | Err: GetErrorReturnValue + Into, 20 | Res: GlResult, 21 | Func: for<'a> Fn(Pin<&'a mut Context>) -> Res, 22 | >( 23 | f: Func, 24 | ) -> Ret { 25 | // optimizer hint for the Some(ptr) case 26 | if_likely! { 27 | // take the current context pointer 28 | // this effectively takes a single-threaded "lock" on the context which protects against 29 | // the user doing Weird Stuff and running multiple GL commands simultaneously 30 | // (i.e. by calling a GL command from the debug callback) 31 | 32 | let Some(ptr) = CTX.take() => { 33 | // need to reassign due to macro jank 34 | let mut ptr = ptr; 35 | // Safety: we are the exclusive accessor of ptr due to its thread locality and the fact that we called `take` on it previously 36 | // wrap the context reference in a pin to ensure it is not moved out of 37 | let mut p = Pin::new(unsafe { ptr.as_mut() }); 38 | let ret = match f(p).into_result() { 39 | Ok(ret) => ret, 40 | Err(e) => { 41 | trace!("GL command execution failed"); 42 | // Safety: f consumes p, the only other exclusive reference to this context, prior to the evaluation of this match arm, 43 | // meaning we are free to create another one to write the error out with 44 | unsafe { ptr.as_mut() }.set_error(e.into()); 45 | // Return the default value for the type 46 | >::get() 47 | } 48 | }; 49 | CTX.set(Some(ptr)); 50 | ret 51 | } else { 52 | panic!("no context set!"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /oxidegl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidegl" 3 | version = "0.0.1" 4 | edition = "2024" 5 | 6 | 7 | [dependencies] 8 | # MacOS deps 9 | 10 | # Objective C Base 11 | objc2 = { workspace = true } 12 | 13 | # Need to speak Metal for... well... doing graphics 14 | objc2-metal = { version = "0.3.0" } 15 | 16 | # Need to speak Cocoa/AppKit to interact with windows and layers 17 | objc2-app-kit = { workspace = true, features = [ 18 | "std", 19 | "objc2-core-foundation", 20 | "NSView", 21 | "NSWindow", 22 | "NSResponder", 23 | "objc2-quartz-core", 24 | ] } 25 | 26 | # Need to speak CoreAnimation for CA(Metal)Layer 27 | objc2-quartz-core = { default-features = false, version = "0.3.0", features = [ 28 | "std", 29 | "objc2-core-foundation", 30 | "objc2-metal", 31 | "CAMetalLayer", 32 | "CALayer", 33 | ] } 34 | # needed for MainThreadMarker/`is_main_thread` and ProcessInfo to get the MacOS version 35 | objc2-foundation = { workspace = true } 36 | 37 | # Need access to raw mach vm_allocate for [MTLDevice newBufferWithBytesNoCopy] 38 | # mach2 = { git = "https://github.com/JohnTitor/mach2/" } 39 | 40 | 41 | # General deps 42 | # FromRepr used for checked GLenum wrapper creation 43 | strum_macros = "0.27.1" 44 | 45 | # Logging 46 | log = { workspace = true } 47 | 48 | # Bitflags for GL Bitmasks 49 | bitflags = "2.6.0" 50 | # Fast hash function 51 | ahash = "0.8.11" 52 | # Macro utilities 53 | concat-idents = "1.1.5" 54 | # Used for const str/[u8] appending for commit hash/version constants 55 | constcat = "0.6.0" 56 | 57 | 58 | # SPIR-V -> MSL conversion 59 | spirv-cross2 = { version = "0.4.4", default-features = false, features = [ 60 | "msl", 61 | ] } 62 | # GLSL -> SPIR-V conversion 63 | glslang = "0.6.1" 64 | 65 | # used to optimize for the hot path in `with_context` 66 | likely_stable = "0.1.2" 67 | # f16, used for texture formats 68 | half = { version = "2.4.1", features = ["bytemuck"] } 69 | bytemuck = { version = "1.23.1", features = ["derive", "extern_crate_alloc", "extern_crate_std"] } 70 | 71 | [build-dependencies] 72 | # you do not want to know what this is for (see build.rs) 73 | deterministic-hash = "1.0.1" 74 | 75 | # Expose max log level features 76 | [features] 77 | max_log_trace = ["log/max_level_trace"] 78 | max_log_debug = ["log/max_level_debug"] 79 | max_log_info = ["log/max_level_info"] 80 | max_log_warn = ["log/max_level_warn"] 81 | max_log_error = ["log/max_level_error"] 82 | max_log_off = ["log/max_level_off"] 83 | 84 | unsound_noerror = [] 85 | -------------------------------------------------------------------------------- /oxidegl/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_op_in_unsafe_fn)] 2 | #![warn(clippy::pedantic, clippy::undocumented_unsafe_blocks)] 3 | #![allow( 4 | clippy::missing_panics_doc, 5 | clippy::module_name_repetitions, 6 | clippy::float_cmp, 7 | clippy::too_many_lines, 8 | clippy::missing_errors_doc, 9 | clippy::match_bool 10 | )] 11 | 12 | use std::sync::Once; 13 | 14 | use context::Context; 15 | use log::info; 16 | 17 | #[allow( 18 | unused_variables, 19 | clippy::wildcard_imports, 20 | clippy::too_many_arguments, 21 | clippy::unused_self, 22 | clippy::similar_names, 23 | clippy::missing_safety_doc 24 | )] 25 | pub mod commands; 26 | pub mod context; 27 | 28 | pub mod debug; 29 | pub mod error; 30 | pub(crate) mod framebuffer; 31 | pub(crate) mod pixel; 32 | pub(crate) mod program; 33 | pub(crate) mod shader; 34 | pub(crate) mod texture; 35 | pub(crate) mod vao; 36 | 37 | pub(crate) mod gl_object; 38 | 39 | pub(crate) mod render; 40 | 41 | #[allow( 42 | clippy::cast_lossless, 43 | clippy::cast_possible_truncation, 44 | clippy::cast_sign_loss, 45 | clippy::cast_precision_loss, 46 | clippy::cast_possible_wrap 47 | )] 48 | pub mod conversions; 49 | 50 | mod device_properties; 51 | 52 | #[allow(non_upper_case_globals, unused)] 53 | pub mod gl_enums; 54 | #[allow(non_snake_case, non_upper_case_globals, clippy::upper_case_acronyms)] 55 | pub mod gl_types; 56 | 57 | pub mod util; 58 | 59 | /// Prints the oxidegl version to stdout and enables some env vars in configurations with debug assertions 60 | /// # Safety 61 | /// Should be called as early in the program execution as possible, as it mutates env vars in a potentially racy way 62 | pub unsafe fn debug_init() { 63 | // wrap in a Once to make sure we don't init twice 64 | static INIT_ONCE: Once = Once::new(); 65 | INIT_ONCE.call_once(|| { 66 | info!("OxideGL {}", Context::VERSION_INFO); 67 | #[cfg(debug_assertions)] 68 | // Safety: We pray that we aren't racing with anyone else's code writing env vars. 69 | // This isn't *too* bad because we're likely running on the main thread, which is where 70 | // a majority of the writes occur in practice. 71 | unsafe { 72 | use std::env::set_var; 73 | 74 | set_var("MTL_DEBUG_LAYER", "1"); 75 | set_var("MTL_SHADER_VALIDATION", "1"); 76 | set_var("MTL_DEBUG_LAYER_VALIDATE_UNRETAINED_RESOURCES", "0x4"); 77 | set_var("RUST_BACKTRACE", "1"); 78 | } 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include "Shader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Util.h" 9 | 10 | void validate_program(GLuint shader) 11 | { 12 | GLint compiled = 0; 13 | glProgramParameteri(shader, GL_PROGRAM_SEPARABLE, GL_TRUE); 14 | glGetProgramiv(shader, GL_LINK_STATUS, &compiled); 15 | if (compiled == GL_FALSE) 16 | { 17 | std::array compiler_log; 18 | glGetProgramInfoLog(shader, compiler_log.size(), nullptr, compiler_log.data()); 19 | glDeleteShader(shader); 20 | 21 | std::ostringstream message; 22 | message << "shader contains error(s):\n\n" << compiler_log.data() << '\n'; 23 | auto str = message.str(); 24 | throw std::runtime_error(str); 25 | } 26 | } 27 | 28 | std::tuple create_program_from_files(std::string_view vert_filepath, std::string_view frag_filepath) 29 | { 30 | auto const vert_source = read_text_file(vert_filepath); 31 | auto const frag_source = read_text_file(frag_filepath); 32 | 33 | return create_program_from_sources(vert_source, frag_source); 34 | } 35 | 36 | std::tuple create_program_from_sources(std::string_view vert_source, std::string_view frag_source) 37 | { 38 | auto const v_ptr = vert_source.data(); 39 | auto const f_ptr = frag_source.data(); 40 | GLuint pipeline = 0; 41 | 42 | auto vert = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &v_ptr); 43 | validate_program(vert); 44 | 45 | auto frag = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &f_ptr); 46 | validate_program(frag); 47 | 48 | glCreateProgramPipelines(1, &pipeline); 49 | glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vert); 50 | glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, frag); 51 | 52 | return std::make_tuple(pipeline, vert, frag); 53 | } 54 | 55 | GLuint create_shader(GLuint vert, GLuint frag) 56 | { 57 | GLuint pipeline = 0; 58 | glCreateProgramPipelines(1, &pipeline); 59 | glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vert); 60 | glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, frag); 61 | return pipeline; 62 | } 63 | 64 | void delete_shader(GLuint pr, GLuint vs, GLuint fs) 65 | { 66 | glDeleteProgramPipelines(1, &pr); 67 | glDeleteProgram(vs); 68 | glDeleteProgram(fs); 69 | } 70 | 71 | // Shader code 72 | const char* blurFragSource = 73 | #include "shader/blur.frag.glsl" 74 | ; 75 | 76 | const char* blurVertSource = 77 | #include "shader/blur.vert.glsl" 78 | ; 79 | 80 | const char* gbufferFragSource = 81 | #include "shader/gbuffer.frag.glsl" 82 | ; 83 | 84 | const char* gbufferVertSource = 85 | #include "shader/gbuffer.vert.glsl" 86 | ; 87 | 88 | const char* mainFragSource = 89 | #include "shader/main.frag.glsl" 90 | ; 91 | 92 | const char* mainVertSource = 93 | #include "shader/main.vert.glsl" 94 | ; -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | struct vertex_t 11 | { 12 | glm::vec3 position = glm::vec3(0.f); 13 | glm::vec3 color = glm::vec3(0.f); 14 | glm::vec3 normal = glm::vec3(0.f); 15 | glm::vec2 texcoord = glm::vec2(0.f); 16 | 17 | vertex_t(glm::vec3 const& position, glm::vec3 const& color, glm::vec3 const& normal, glm::vec2 const& texcoord) 18 | : position(position), color(color), normal(normal), texcoord(texcoord) {} 19 | }; 20 | 21 | struct attrib_format_t 22 | { 23 | GLuint attrib_index = 0; 24 | GLint size = 0; 25 | GLenum type = 0; 26 | GLuint relative_offset = 0; 27 | }; 28 | 29 | template 30 | constexpr std::pair type_to_size_enum() 31 | { 32 | if constexpr (std::is_same_v) 33 | return std::make_pair(1, GL_FLOAT); 34 | if constexpr (std::is_same_v) 35 | return std::make_pair(1, GL_INT); 36 | if constexpr (std::is_same_v) 37 | return std::make_pair(1, GL_UNSIGNED_INT); 38 | if constexpr (std::is_same_v) 39 | return std::make_pair(2, GL_FLOAT); 40 | if constexpr (std::is_same_v) 41 | return std::make_pair(3, GL_FLOAT); 42 | if constexpr (std::is_same_v) 43 | return std::make_pair(4, GL_FLOAT); 44 | throw std::runtime_error("unsupported type"); 45 | } 46 | 47 | template 48 | inline attrib_format_t create_attrib_format(GLuint attrib_index, GLuint relative_offset) 49 | { 50 | auto const [comp_count, type] = type_to_size_enum(); 51 | return attrib_format_t{ attrib_index, comp_count, type, relative_offset }; 52 | } 53 | 54 | template 55 | inline GLuint create_buffer(std::vector const& buff, GLenum flags = GL_DYNAMIC_STORAGE_BIT) 56 | { 57 | GLuint name = 0; 58 | glCreateBuffers(1, &name); 59 | glNamedBufferStorage(name, sizeof(typename std::vector::value_type) * buff.size(), buff.data(), flags); 60 | return name; 61 | } 62 | 63 | template 64 | std::tuple create_geometry(std::vector const& vertices, std::vector const& indices, std::vector const& attrib_formats) 65 | { 66 | GLuint vao = 0; 67 | auto vbo = create_buffer(vertices); 68 | auto ibo = create_buffer(indices); 69 | 70 | glCreateVertexArrays(1, &vao); 71 | glVertexArrayVertexBuffer(vao, 0, vbo, 0, sizeof(T)); 72 | glVertexArrayElementBuffer(vao, ibo); 73 | 74 | for (auto const& format : attrib_formats) 75 | { 76 | glEnableVertexArrayAttrib(vao, format.attrib_index); 77 | glVertexArrayAttribFormat(vao, format.attrib_index, format.size, format.type, GL_FALSE, format.relative_offset); 78 | glVertexArrayAttribBinding(vao, format.attrib_index, 0); 79 | } 80 | 81 | return std::make_tuple(vao, vbo, ibo); 82 | } 83 | 84 | std::vector calc_tangents(std::vector const& vertices, std::vector const& indices); -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void validate_program(GLuint shader); 12 | 13 | std::tuple create_program_from_files(std::string_view vert_filepath, std::string_view frag_filepath); 14 | 15 | std::tuple create_program_from_sources(std::string_view vert_source, std::string_view frag_source); 16 | 17 | GLuint create_shader(GLuint vert, GLuint frag); 18 | 19 | void delete_shader(GLuint pr, GLuint vs, GLuint fs); 20 | 21 | template 22 | void set_uniform(GLuint shader, GLint location, T const& value) 23 | { 24 | if constexpr (std::is_same_v) glProgramUniform1i(shader, location, value); 25 | else if constexpr (std::is_same_v) glProgramUniform1ui(shader, location, value); 26 | else if constexpr (std::is_same_v) glProgramUniform1ui(shader, location, value); 27 | else if constexpr (std::is_same_v) glProgramUniform1f(shader, location, value); 28 | else if constexpr (std::is_same_v) glProgramUniform1d(shader, location, value); 29 | else if constexpr (std::is_same_v) glProgramUniform2fv(shader, location, 1, glm::value_ptr(value)); 30 | else if constexpr (std::is_same_v) glProgramUniform3fv(shader, location, 1, glm::value_ptr(value)); 31 | else if constexpr (std::is_same_v) glProgramUniform4fv(shader, location, 1, glm::value_ptr(value)); 32 | else if constexpr (std::is_same_v) glProgramUniform2iv(shader, location, 1, glm::value_ptr(value)); 33 | else if constexpr (std::is_same_v) glProgramUniform3iv(shader, location, 1, glm::value_ptr(value)); 34 | else if constexpr (std::is_same_v) glProgramUniform4iv(shader, location, 1, glm::value_ptr(value)); 35 | else if constexpr (std::is_same_v) glProgramUniform2uiv(shader, location, 1, glm::value_ptr(value)); 36 | else if constexpr (std::is_same_v) glProgramUniform3uiv(shader, location, 1, glm::value_ptr(value)); 37 | else if constexpr (std::is_same_v) glProgramUniform4uiv(shader, location, 1, glm::value_ptr(value)); 38 | else if constexpr (std::is_same_v) glProgramUniform4fv(shader, location, 1, glm::value_ptr(value)); 39 | else if constexpr (std::is_same_v) glProgramUniformMatrix3fv(shader, location, 1, GL_FALSE, glm::value_ptr(value)); 40 | else if constexpr (std::is_same_v) glProgramUniformMatrix4fv(shader, location, 1, GL_FALSE, glm::value_ptr(value)); 41 | else throw std::runtime_error("unsupported type"); 42 | } 43 | 44 | // Shader source code 45 | extern const char* blurFragSource; 46 | extern const char* blurVertSource; 47 | extern const char* gbufferFragSource; 48 | extern const char* gbufferVertSource; 49 | extern const char* mainFragSource; 50 | extern const char* mainVertSource; -------------------------------------------------------------------------------- /xtask/implemented.txt: -------------------------------------------------------------------------------- 1 | // GL functions to skip generating placeholder code 2 | // note that 3 | // p: - mark all functions documented by an OpenGL Reference Page with the 4 | // name converted to snake_case (e.g. glGetString becomes gl_get_string) as implemented 5 | // f: - mark individual GL command (by originalGlCommandName) as implemented. 6 | 7 | //glGet 8 | p:gl_get 9 | p:gl_get_string 10 | 11 | //TODO: getInternalformat 12 | 13 | //Buffers 14 | //Buffer lifecycle 15 | f:glCreateBuffers 16 | f:glGenBuffers 17 | f:glIsBuffer 18 | f:glDeleteBuffers 19 | //Buffer manipulation 20 | f:glBufferStorage 21 | f:glNamedBufferStorage 22 | f:glBindBuffer 23 | f:glBindBufferBase 24 | f:glBindBufferRange 25 | f:glBindBuffersBase 26 | f:glBindBuffersRange 27 | 28 | // caps 29 | f:glEnable 30 | f:glDisable 31 | f:glEnablei 32 | f:glDisablei 33 | 34 | // debug 35 | f:glObjectLabel 36 | f:glObjectPtrLabel 37 | f:glGetObjectLabel 38 | f:glGetObjectPtrLabel 39 | f:glGetPointerv 40 | f:glDebugMessageCallback 41 | f:glDebugMessageControl 42 | f:glDebugMessageInsert 43 | f:glGetDebugMessageLog 44 | f:glPushDebugGroup 45 | f:glPopDebugGroup 46 | 47 | 48 | // some draws 49 | f:glDrawArrays 50 | f:glDrawArraysIndirect 51 | f:glDrawArraysInstanced 52 | f:glDrawArraysInstancedBaseInstance 53 | f:glDrawElements 54 | f:glDrawElementsBaseVertex 55 | f:glDrawElementsIndirect 56 | f:glDrawElementsInstanced 57 | f:glDrawElementsInstancedBaseInstance 58 | f:glDrawElementsInstancedBaseVertex 59 | f:glDrawElementsInstancedBaseVertexBaseInstance 60 | f:glDrawRangeElements 61 | f:glDrawRangeElementsBaseVertex 62 | f:glDrawTransformFeedback 63 | f:glDrawTransformFeedbackInstanced 64 | f:glDrawTransformFeedbackStream 65 | f:glDrawTransformFeedbackStreamInstanced 66 | 67 | 68 | // glClear and co. 69 | p:gl_clear 70 | p:gl_clear_depth 71 | f:glClearColor 72 | f:glClearStencil 73 | 74 | f:glScissor 75 | f:glScissorArrayv 76 | f:glViewport 77 | f:glViewportArrayv 78 | 79 | // VAO manipulation 80 | p:gl_vertex_attrib_format 81 | p:gl_bind_vertex_buffer 82 | p:gl_bind_vertex_buffers 83 | p:gl_enable_vertex_attrib_array 84 | p:gl_vertex_attrib_binding 85 | p:gl_vertex_attrib_pointer 86 | 87 | // VAO divisor 88 | f:glVertexBindingDivisor 89 | f:glVertexArrayBindingDivisor 90 | 91 | // VAO lifecycle 92 | f:glCreateVertexArrays 93 | f:glGenVertexArrays 94 | f:glBindVertexArray 95 | f:glIsVertexArray 96 | f:glDeleteVertexArrays 97 | 98 | // Shaders 99 | f:glCreateShader 100 | f:glDeleteShader 101 | f:glGetShaderiv 102 | 103 | 104 | f:glCreateProgram 105 | f:glLinkProgram 106 | f:glAttachShader 107 | f:glDetachShader 108 | f:glGetProgramiv 109 | f:glValidateProgram 110 | f:glUseProgram 111 | 112 | 113 | //Shader manipulation 114 | f:glShaderSource 115 | f:glCompileShader 116 | 117 | f:glGetError 118 | 119 | p:gl_get_renderbuffer_parameter 120 | p:gl_get_sampler_parameter 121 | p:gl_get_tex_level_parameter 122 | p:gl_get_tex_parameter 123 | p:gl_tex_parameter 124 | 125 | p:gl_tex_buffer 126 | p:gl_tex_buffer_range 127 | 128 | p:gl_get_tex_image 129 | p:gl_tex_storage_1_d 130 | p:gl_tex_storage_2_d 131 | p:gl_tex_storage_2_d_multisample 132 | p:gl_tex_storage_3_d 133 | p:gl_tex_storage_3_d_multisample 134 | 135 | p:gl_tex_sub_image_1_d 136 | p:gl_tex_sub_image_2_d 137 | p:gl_tex_sub_image_3_d 138 | f:glCreateTextures 139 | f:glGenTextures 140 | f:glIsTexture 141 | f:glBindTexture 142 | f:glBindTextures 143 | f:glBindTextureUnit 144 | -------------------------------------------------------------------------------- /oxidegl/src/framebuffer.rs: -------------------------------------------------------------------------------- 1 | use objc2_metal::MTLTexture; 2 | 3 | use crate::{ 4 | gl_enums::{DrawBufferMode, TextureTarget}, 5 | util::ProtoObjRef, 6 | }; 7 | 8 | use super::gl_object::{LateInit, NamedObject, ObjectName}; 9 | 10 | pub const MAX_COLOR_ATTACHMENTS: u32 = 8; 11 | #[derive(Debug)] 12 | pub struct Framebuffer { 13 | name: ObjectName, 14 | draw_buffers: DrawBuffers, 15 | color_attachments: [Option; MAX_COLOR_ATTACHMENTS as usize], 16 | depth_attachment: Option, 17 | stencil_attachment: Option, 18 | } 19 | impl Framebuffer { 20 | pub fn new_default(name: ObjectName) -> Self { 21 | Self { 22 | name, 23 | draw_buffers: DrawBuffers::new(), 24 | color_attachments: [const { None }; MAX_COLOR_ATTACHMENTS as usize], 25 | depth_attachment: None, 26 | stencil_attachment: None, 27 | } 28 | } 29 | } 30 | 31 | impl NamedObject for Framebuffer { 32 | type LateInitType = LateInit; 33 | const LATE_INIT_FUNC: fn(ObjectName) -> Self = Self::new_default; 34 | } 35 | /// GL object wrapping an internal drawable 36 | pub(crate) struct RenderBuffer { 37 | pub(crate) name: ObjectName, 38 | pub(crate) drawable: InternalDrawable, 39 | } 40 | #[derive(Debug, Clone, Copy)] 41 | pub(crate) enum ClearValue { 42 | // float/normalized color 43 | Float([f32; 4]), 44 | // integer (might be bitcast i32) color 45 | Integer([u32; 4]), 46 | 47 | // depth 48 | Depth(f32), 49 | // stencil 50 | Stencil(i32), 51 | } 52 | pub trait AttachableTexture {} 53 | impl AttachableTexture for RenderBuffer {} 54 | 55 | // TODO uncomment when Texture is impled :3 56 | // impl AttachableTexture for Texture {} 57 | #[derive(Debug)] 58 | pub(crate) struct FramebufferAttachment { 59 | pub(crate) clear: Option, 60 | pub(crate) target: TextureTarget, 61 | pub(crate) tex_name: ObjectName, 62 | } 63 | #[derive(Debug, Clone, Copy)] 64 | pub(crate) struct DrawBuffers { 65 | pub(crate) modes: [Option; MAX_COLOR_ATTACHMENTS as usize], 66 | } 67 | impl Default for DrawBuffers { 68 | fn default() -> Self { 69 | let mut s = Self::new(); 70 | s.modes[0] = Some(DrawBufferMode::FrontLeft); 71 | s 72 | } 73 | } 74 | 75 | impl DrawBuffers { 76 | pub(crate) fn new() -> Self { 77 | Self { 78 | modes: [None; MAX_COLOR_ATTACHMENTS as usize], 79 | } 80 | } 81 | pub(crate) fn sanitize_mode(mode: DrawBufferMode) -> Option { 82 | match mode { 83 | DrawBufferMode::None => None, 84 | v => Some(v), 85 | } 86 | } 87 | 88 | pub(crate) fn drawbuf_iter(&self) -> impl Iterator + '_ { 89 | self.modes 90 | .iter() 91 | .copied() 92 | .take_while(std::option::Option::is_some) 93 | .flatten() 94 | } 95 | } 96 | /// A drawable for usage in rendering 97 | #[derive(Debug, Clone)] 98 | pub(crate) struct InternalDrawable { 99 | // TODO might not need this field (dims are tracked by the texture object) 100 | pub(crate) dimensions: (u32, u32), 101 | pub(crate) tex: ProtoObjRef, 102 | } 103 | impl InternalDrawable { 104 | pub(crate) fn new(color: ProtoObjRef, dimensions: (u32, u32)) -> Self { 105 | Self { 106 | dimensions, 107 | tex: color, 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/fendevel_moderngl/source/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | 3 | #include 4 | #include 5 | 6 | #define STB_IMAGE_IMPLEMENTATION 7 | #include 8 | #include 9 | 10 | GLuint create_texture_2d(GLenum internal_format, GLenum format, GLsizei width, 11 | GLsizei height, void *data, GLenum filter, 12 | GLenum repeat, bool generateMipMaps) { 13 | GLuint tex = 0; 14 | glCreateTextures(GL_TEXTURE_2D, 1, &tex); 15 | int levels = 1; 16 | if (generateMipMaps) { 17 | levels = 1 + (int)std::floor(std::log2(std::min(width, height))); 18 | } 19 | glTextureStorage2D(tex, levels, internal_format, width, height); 20 | 21 | if (generateMipMaps) { 22 | if (filter == GL_LINEAR) 23 | glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 24 | else if (filter == GL_NEAREST) 25 | glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, 26 | GL_NEAREST_MIPMAP_NEAREST); 27 | else 28 | throw std::runtime_error("Unsupported filter"); 29 | 30 | } else { 31 | glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, filter); 32 | } 33 | glTextureParameteri(tex, GL_TEXTURE_MAG_FILTER, filter); 34 | 35 | glTextureParameteri(tex, GL_TEXTURE_WRAP_S, repeat); 36 | glTextureParameteri(tex, GL_TEXTURE_WRAP_T, repeat); 37 | glTextureParameteri(tex, GL_TEXTURE_WRAP_R, repeat); 38 | 39 | if (data) { 40 | glTextureSubImage2D(tex, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, 41 | data); 42 | } 43 | 44 | if (generateMipMaps) 45 | glGenerateTextureMipmap(tex); 46 | 47 | return tex; 48 | } 49 | 50 | GLuint create_texture_2d_from_file(std::string_view filepath, stb_comp_t comp, 51 | bool generateMipMaps) { 52 | int x, y, c; 53 | if (!std::filesystem::exists(filepath.data())) { 54 | std::ostringstream message; 55 | message << "file " << filepath.data() << " does not exist."; 56 | throw std::runtime_error(message.str()); 57 | } 58 | const auto data = stbi_load(filepath.data(), &x, &y, &c, comp); 59 | 60 | auto const [in, ex] = [comp]() { 61 | switch (comp) { 62 | case STBI_rgb_alpha: 63 | return std::make_pair(GL_RGBA8, GL_RGBA); 64 | case STBI_rgb: 65 | return std::make_pair(GL_RGB8, GL_RGB); 66 | case STBI_grey: 67 | return std::make_pair(GL_R8, GL_RED); 68 | case STBI_grey_alpha: 69 | return std::make_pair(GL_RG8, GL_RG); 70 | default: 71 | throw std::runtime_error("invalid format"); 72 | } 73 | }(); 74 | 75 | const auto name = create_texture_2d(in, ex, x, y, data, GL_LINEAR, GL_REPEAT, 76 | generateMipMaps); 77 | stbi_image_free(data); 78 | return name; 79 | } 80 | 81 | GLuint 82 | create_texture_cube_from_file(std::array const &filepath, 83 | stb_comp_t comp) { 84 | int x, y, c; 85 | std::array faces; 86 | 87 | auto const [in, ex] = [comp]() { 88 | switch (comp) { 89 | case STBI_rgb_alpha: 90 | return std::make_pair(GL_RGBA8, GL_RGBA); 91 | case STBI_rgb: 92 | return std::make_pair(GL_RGB8, GL_RGB); 93 | case STBI_grey: 94 | return std::make_pair(GL_R8, GL_RED); 95 | case STBI_grey_alpha: 96 | return std::make_pair(GL_RG8, GL_RG); 97 | default: 98 | throw std::runtime_error("invalid format"); 99 | } 100 | }(); 101 | 102 | for (auto i = 0; i < 6; i++) { 103 | faces[i] = stbi_load(filepath[i].data(), &x, &y, &c, comp); 104 | } 105 | 106 | const auto name = create_texture_cube(in, ex, x, y, faces); 107 | 108 | for (auto face : faces) { 109 | stbi_image_free(face); 110 | } 111 | return name; 112 | } -------------------------------------------------------------------------------- /oxidegl/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | env, 4 | fs::{read_dir, File, ReadDir}, 5 | hash::{DefaultHasher, Hash, Hasher}, 6 | io::{BufWriter, Write}, 7 | path::Path, 8 | process::Command, 9 | }; 10 | 11 | use deterministic_hash::DeterministicHasher; 12 | 13 | fn main() { 14 | let commit_hash = Command::new("git") 15 | .args(["rev-parse", "HEAD"]) 16 | .output() 17 | .ok() 18 | .and_then(|out| String::from_utf8(out.stdout).ok()) 19 | .unwrap_or("Commit hash N/A".to_string()); 20 | println!("cargo:rustc-env=OXIDEGL_COMMIT_HASH={commit_hash}"); 21 | println!("cargo:rerun-if-changed=.git/HEAD"); 22 | println!("cargo:rerun-if-changed=build.rs"); 23 | 24 | let mut s = "oxidegl/src".to_string(); 25 | let mut v = Vec::new(); 26 | search_dir( 27 | read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src")).unwrap(), 28 | &mut s, 29 | &mut v, 30 | ); 31 | v.sort(); 32 | let mut set = HashSet::new(); 33 | let mut tvec = Vec::new(); 34 | for s in v { 35 | let mut h = DeterministicHasher::new(DefaultHasher::new()); 36 | s.hash(&mut h); 37 | let mut id = (h.finish() & 0xFFFF) as u16; 38 | loop { 39 | if !set.insert(id) { 40 | id = id.wrapping_add(1); 41 | } else { 42 | break; 43 | } 44 | } 45 | tvec.push((id, s)); 46 | } 47 | tvec.sort_by(|lhs, rhs| match compare_strings(&lhs.1, &rhs.1) { 48 | 1 => std::cmp::Ordering::Greater, 49 | 0 => std::cmp::Ordering::Equal, 50 | -1 => std::cmp::Ordering::Less, 51 | _ => unreachable!(), 52 | }); 53 | let string = format!( 54 | "const FNAME_LOOKUP: ConstStrToU16Map<{}> = ConstStrToU16Map {{ 55 | vals: [{}], 56 | keys: [\"{}\"], 57 | 58 | }};", 59 | tvec.len(), 60 | tvec.iter() 61 | .map(|v| v.0.to_string()) 62 | .collect::>() 63 | .join(", "), 64 | tvec.into_iter() 65 | .map(|v| v.1) 66 | .collect::>() 67 | .join("\", \"") 68 | ); 69 | let mut f = BufWriter::new( 70 | File::create(Path::new(&env::var("OUT_DIR").unwrap()).join("generated.rs")).unwrap(), 71 | ); 72 | write!(&mut f, "{}", &string).unwrap(); 73 | } 74 | const fn min_usize(a: usize, b: usize) -> usize { 75 | if a < b { 76 | a 77 | } else { 78 | b 79 | } 80 | } 81 | const fn compare_strings(a: &str, b: &str) -> i32 { 82 | let (a, b) = (a.as_bytes(), b.as_bytes()); 83 | let max_idx = min_usize(a.len(), b.len()); 84 | let mut i = 0; 85 | while i < max_idx { 86 | if a[i] > b[i] { 87 | return 1; 88 | } 89 | if a[i] < b[i] { 90 | return -1; 91 | } 92 | i += 1; 93 | } 94 | if a.len() > b.len() { 95 | return 1; 96 | } 97 | if a.len() < b.len() { 98 | return -1; 99 | } 100 | 0 101 | } 102 | fn search_dir(dir: ReadDir, current_prefix: &mut String, paths: &mut Vec) { 103 | for f in dir { 104 | let Ok(e) = f else { 105 | continue; 106 | }; 107 | let t = e.file_type().unwrap(); 108 | let osname = e.file_name(); 109 | let fname = osname.to_str().unwrap(); 110 | if t.is_dir() { 111 | current_prefix.push('/'); 112 | 113 | current_prefix.push_str(fname); 114 | search_dir(read_dir(e.path()).unwrap(), current_prefix, paths); 115 | current_prefix.truncate(current_prefix.len() - fname.len() - 1); 116 | } 117 | if t.is_file() && fname.ends_with(".rs") { 118 | paths.push(format!("{current_prefix}/{}", fname)); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OxideGL 2 | An open-source OpenGL 4.6 Core implementation atop Apple's Metal API, written in Rust 3 | 4 | ## Project components at a glance 5 | * `oxidegl`: main Rust crate. Contains the implementations of all GL commands and the Context struct. Compiles to a Rust library. 6 | * `oxidegl_c`: Depends on the main `oxidegl` crate and provides C ABI shims for all of the GL commands, as well as implementing a basic C interface for context creation. Compiles to a C dylib. 7 | * `oxidegl_shim`: Depends on `oxidegl_c` and `oxidegl`, uses shenanigans to replace the system NSGL/CGL implementation with oxidegl from a static constructor. Compiles to a C dylib. 8 | * `xtask`: Contains various utilities for working with oxidegl and its GLFW fork, as well as code generation scripts for various parts of the GL. 9 | * `oxidegl-glfw`: fork of glfw which adds support for oxidegl via `oxidegl_c`. 10 | 11 | ## Building/XTasks 12 | To get a full list of OxideGL `xtask` subcommands, run `cargo xtask --help` anywhere in this repository. 13 | All tasks implicitly run their dependencies (e.g. `cargo xtask build-glfw` implies `cargo xtask gen-glfw-build` etc), so you don't need to run dependencies of tasks manually. 14 | 15 | ## Current State 16 | * xtask system capable of: 17 | * building and OxideGL and its GLFW fork and running other utilities 18 | * code generation of a placeholder GL implementation, as well as Rust enums for the allowed parameters of many functions that take `GLenum` 19 | * Context creation and linkage with GLFW 20 | * Generic context parameter lookup (`glGet` and co.) 21 | * full implementation of VAOs (some features disabled due to limitations in shader translation) 22 | * initial implementation of buffers and buffer binding (currently missing buffer copy operations) 23 | * initial implementation of shaders and shader programs 24 | * initial implementation of shader translation using the `glslang` and `spirv_cross2` binding crates 25 | * initial GL state -> metal state translation 26 | 27 | ## Goals 28 | * Easy to develop and hack on 29 | * builds a working artifact with a simple `cargo build` 30 | * reasonable initial build times 31 | * focus on safety and correctness before maximum performance (prefer safe code, optimize where needed with unsafe later) 32 | * Run Minecraft 33 | * Have decent performance 34 | * Automate as much tedium as possible by leveraging OpenGL's machine-readable spec and reference 35 | 36 | ## Non-Goals 37 | * Full OpenGL 4.6 compliance (Metal itself is not specified strongly enough for this to be worthwhile) 38 | * Multithreading support/share context (this may actually be practical, `Context` is currently still `Send`) 39 | 40 | ## Version Requirements 41 | MSRV is the latest stable release. 42 | Minimum supported Metal version is Metal 2.2 (corresponds to MacOS 10.15 and iOS 13) 43 | 44 | ## Linting 45 | This project uses Clippy for linting. If you use VS Code or a derivative thereof, this should be enabled already (via a `.vscode` with the appropriate configuration in the repository root). If not, check if your IDE supports changing the rust analyzer check command or simply run `cargo clippy` from your shell. 46 | 47 | //TODO: move this into another file and update (errors have been partially implemented since this was written) 48 | ## UB Propagation, Errors and `GL_NOERROR` 49 | Since this implementation only provides a `GL_NOERROR` context, its behavior in an errored state may be undefined according to the GL spec. In release builds, upon recieving erroring input, OxideGL will propagate UB (i.e. raise GL UB to Rust language UB) where there is a measurable and significant performance advantage in doing so (e.g. skipping array bounds checking in a hot path), otherwise it will abort the calling program. However, in debug builds, OxideGL will attempt to abort with a helpful message (and a stack trace including the calling function that caused the error) as soon as possible after recieving an incorrect input. -------------------------------------------------------------------------------- /oxidegl_c/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{env, ffi::c_void, ptr::NonNull}; 2 | 3 | use flexi_logger::Logger; 4 | use log::{debug, trace}; 5 | use objc2::rc::Retained; 6 | use objc2_app_kit::NSView; 7 | 8 | use context::{CTX, with_ctx_mut}; 9 | use oxidegl::{context::Context, gl_types::GLenum}; 10 | pub mod context; 11 | #[allow( 12 | unused_variables, 13 | non_snake_case, 14 | dead_code, 15 | non_upper_case_globals, 16 | unused_mut, 17 | clippy::module_name_repetitions, 18 | // needed to pull inferred return type down to () 19 | clippy::semicolon_if_nothing_returned, clippy::unit_arg, 20 | clippy::wildcard_imports, 21 | clippy::missing_safety_doc, 22 | )] 23 | pub mod gl_core; 24 | 25 | // Safety: we pray no one is using this symbol name, and prefix our function name with oxidegl 26 | #[unsafe(no_mangle)] 27 | /// # Safety 28 | /// if ctx is Some(a) then a must point to a valid, initalized [`Context`] 29 | pub unsafe extern "C" fn oxidegl_set_current_context(ctx: Option>) { 30 | set_context(ctx); 31 | } 32 | pub fn set_context(ctx: Option>) { 33 | if let Some(mut prev) = CTX.take() { 34 | // early return if we are setting the same context again 35 | if ctx == Some(prev) { 36 | return; 37 | } 38 | // Safety: we are the exclusive accessor of this context, as we just removed it from CTX 39 | unsafe { prev.as_mut() }.made_not_current(); 40 | } 41 | if let Some(mut c) = ctx { 42 | // Safety: we are the exclusive accessor of this context, because it has not been installed to CTX yet 43 | unsafe { c.as_mut() }.made_current(); 44 | CTX.set(ctx); 45 | } 46 | if ctx.is_some() { 47 | trace!("set context to {:?}", ctx); 48 | } 49 | } 50 | pub fn swap_buffers() { 51 | with_ctx_mut(|mut r| r.swap_buffers()); 52 | trace!("swap buffers"); 53 | } 54 | // Safety: we pray no one is using this symbol name, and prefix our function name with oxidegl 55 | #[unsafe(no_mangle)] 56 | /// # Safety 57 | /// if ctx is Some(a) then a must point to a valid, initalized [`Context`] 58 | pub unsafe extern "C" fn oxidegl_swap_buffers(_ctx: Option>) { 59 | swap_buffers(); 60 | } 61 | #[must_use] 62 | pub fn box_ctx(ctx: Context) -> NonNull { 63 | let p = Box::into_raw(Box::new(ctx)); 64 | // Safety: Box guarantees that the pointer is non-null 65 | unsafe { NonNull::new_unchecked(p) } 66 | } 67 | // Safety: we pray no one is using this symbol name, and prefix our function name with oxidegl 68 | #[unsafe(no_mangle)] 69 | /// # Safety 70 | /// This needs to be run as early as possible (ideally before the program spawns a thread other than the main thread) 71 | pub unsafe extern "C" fn oxidegl_platform_init() { 72 | init_logger(); 73 | unsafe { 74 | oxidegl::debug_init(); 75 | } 76 | } 77 | pub(crate) fn init_logger() { 78 | if !env::var("OXIDEGL_LOG_TO_STDOUT").is_ok_and(|v| v == "0") { 79 | Logger::try_with_env_or_str("none, oxidegl=trace") 80 | .unwrap() 81 | .log_to_stdout() 82 | .start() 83 | .unwrap(); 84 | log::trace!("OxideGL stdout logger initialized"); 85 | } 86 | } 87 | 88 | // Safety: we pray no one is using this symbol name, and prefix our function name with oxidegl 89 | #[unsafe(no_mangle)] 90 | /// # Safety 91 | /// This function must be called at most once per allocated context to avoid a double free 92 | /// ctx, if Some must point to a valid Context and be exclusive 93 | pub unsafe extern "C" fn oxidegl_destroy_context(ctx: Option>) { 94 | if let Some(c) = ctx { 95 | // Safety: caller ensures c points to a valid Context struct, allocated via a Box with the same allocator 96 | // Caller ensures c is the exclusive pointer to ctx 97 | drop(unsafe { Box::from_raw(c.as_ptr()) }); 98 | } 99 | } 100 | // Safety: we pray no one is using this symbol name, and prefix our function name with oxidegl 101 | #[unsafe(no_mangle)] 102 | /// # Safety 103 | /// view must point to a valid instance of NSView 104 | pub unsafe extern "C" fn oxidegl_create_context( 105 | view: *mut NSView, 106 | // TODO make these params do something idk 107 | _format: GLenum, 108 | _typ: GLenum, 109 | _depth_format: GLenum, 110 | _depth_type: GLenum, 111 | _stencil_format: GLenum, 112 | _stencil_type: GLenum, 113 | ) -> *mut c_void { 114 | let mut ctx = Context::new(); 115 | // Safety: caller ensures ptr is a pointer to a valid, initialized NSView. 116 | let view = unsafe { Retained::retain(view).unwrap() }; 117 | 118 | ctx.set_view(&view); 119 | debug!("Created context"); 120 | box_ctx(ctx).as_ptr().cast() 121 | } 122 | -------------------------------------------------------------------------------- /oxidegl/src/util.rs: -------------------------------------------------------------------------------- 1 | pub type ProtoObjRef = Retained>; 2 | use std::cell::Cell; 3 | use std::fmt::Debug; 4 | use std::num::NonZeroU32; 5 | #[must_use] 6 | /// Prints the trimmed type name (e.g. with all paths removed). May not work correctly in all cases (likely breaks for local structs, futures, closures etc) 7 | /// # Stability note 8 | /// The output of this function is not guaranteed to be stable across rust versions (i.e. we forward the *lack* of stability guarantees inherent to [`std::any::type_name`]) 9 | /// As such, all usages of [`trimmed_type_name`] should be purely for programmer-facing debug output, 10 | /// program behavior should not depend on the contents of the output. 11 | pub(crate) fn trimmed_type_name() -> &'static str { 12 | let s = std::any::type_name::(); 13 | 14 | let mut last_ident_start_index = 0; 15 | let mut last_substr = ""; 16 | let mut gen_flag = false; 17 | 18 | for substr in s.split("::") { 19 | last_substr = substr; 20 | if substr.contains('<') { 21 | gen_flag = true; 22 | break; 23 | } 24 | last_ident_start_index += substr.len() + 2; 25 | } 26 | if gen_flag { 27 | &s[last_ident_start_index..] 28 | } else { 29 | last_substr 30 | } 31 | } 32 | 33 | pub struct NoDebug { 34 | inner: T, 35 | } 36 | 37 | impl Debug for NoDebug { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | let n = trimmed_type_name::(); 40 | f.debug_struct(&format!("skipped debug for {n}")).finish() 41 | } 42 | } 43 | 44 | impl From for NoDebug { 45 | fn from(value: T) -> Self { 46 | Self { inner: value } 47 | } 48 | } 49 | 50 | impl Deref for NoDebug { 51 | type Target = T; 52 | 53 | fn deref(&self) -> &Self::Target { 54 | &self.inner 55 | } 56 | } 57 | 58 | impl DerefMut for NoDebug { 59 | fn deref_mut(&mut self) -> &mut Self::Target { 60 | &mut self.inner 61 | } 62 | } 63 | 64 | impl Default for NoDebug 65 | where 66 | T: Default, 67 | { 68 | fn default() -> Self { 69 | Self { 70 | inner: T::default(), 71 | } 72 | } 73 | } 74 | 75 | macro_rules! bitflag_bits { 76 | { 77 | $( #[$attr:meta] )* 78 | $v:vis struct $name:ident: $t:ident bits: { 79 | $( 80 | $( #[doc = $doc:literal] )* 81 | $bit_name:ident: $bit:expr 82 | ),+ $(,)? 83 | } 84 | } => { 85 | ::bitflags::bitflags! { 86 | $(#[$attr])* 87 | $v struct $name: $t { 88 | $( 89 | $( #[doc = $doc] )* 90 | const $bit_name = 1 << $bit);+ 91 | ; 92 | } 93 | } 94 | } 95 | } 96 | 97 | pub(crate) use bitflag_bits; 98 | 99 | use std::mem::ManuallyDrop; 100 | use std::ops::{Deref, DerefMut}; 101 | 102 | /// [`unreachable!`](unreachable!), but it reduces to [`core::hint::unreachable_unchecked()`] in builds without debug assertions. 103 | macro_rules! debug_unreachable { 104 | ($($msg:tt)*) => { 105 | { 106 | // Need to do something unsafe to surface this macro's unsafety in builds with debug assertions enabled 107 | #[allow(clippy::useless_transmute)] 108 | let _: () = ::core::mem::transmute(()); 109 | #[cfg(debug_assertions)] 110 | unreachable!($($msg)*); 111 | #[cfg(not(debug_assertions))] 112 | ::core::hint::unreachable_unchecked() 113 | } 114 | }; 115 | } 116 | 117 | pub(crate) use debug_unreachable; 118 | 119 | /// takes in a place expression, a value expression, and an action 120 | macro_rules! run_if_changed { 121 | ( $place:expr ;= $new_val:expr => $action:expr ) => { 122 | if $place != $new_val { 123 | $place = $new_val; 124 | $action 125 | } 126 | }; 127 | } 128 | use objc2::{rc::Retained, runtime::ProtocolObject}; 129 | pub(crate) use run_if_changed; 130 | 131 | pub(crate) const unsafe fn transmute_unchecked(value: Src) -> Dst { 132 | union Transmute { 133 | src: ManuallyDrop, 134 | dst: ManuallyDrop, 135 | } 136 | // Safety: caller 137 | ManuallyDrop::into_inner(unsafe { 138 | Transmute { 139 | src: ManuallyDrop::new(value), 140 | } 141 | .dst 142 | }) 143 | } 144 | pub(crate) struct CloneOptionCell { 145 | inner: Cell>, 146 | } 147 | impl Clone for CloneOptionCell { 148 | fn clone(&self) -> Self { 149 | let v = self.inner.take(); 150 | self.inner.set(v.clone()); 151 | Self { 152 | inner: Cell::new(v), 153 | } 154 | } 155 | } 156 | impl Debug for CloneOptionCell { 157 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 158 | f.debug_struct("CloneOptionCell") 159 | .field("inner", &self.clone_out()) 160 | .finish() 161 | } 162 | } 163 | impl CloneOptionCell { 164 | pub(crate) fn clone_out(&self) -> Option 165 | where 166 | T: Clone, 167 | { 168 | let out = self.inner.take(); 169 | self.inner.set(out.clone()); 170 | out 171 | } 172 | pub(crate) fn set(&self, val: Option) { 173 | self.inner.set(val); 174 | } 175 | pub(crate) fn new(val: Option) -> Self { 176 | Self { 177 | inner: Cell::new(val), 178 | } 179 | } 180 | } 181 | pub(crate) trait ToU32 { 182 | fn to_u32(self) -> u32; 183 | } 184 | impl ToU32 for Option { 185 | fn to_u32(self) -> u32 { 186 | // Safety: guaranteed by Option repr 187 | unsafe { std::mem::transmute(self) } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /oxidegl/src/shader.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, mem}; 2 | 3 | use crate::{gl_enums::ShaderType, util::NoDebug}; 4 | use glslang::{ 5 | Compiler as GlslangCompiler, CompilerOptions, Shader as GlslLangShader, ShaderInput, 6 | ShaderMessage, ShaderSource, ShaderStage, SourceLanguage, Target, 7 | }; 8 | // use naga::{ 9 | // front::glsl, 10 | // valid::{Capabilities, ModuleInfo, ValidationFlags, Validator}, 11 | // ShaderStage, WithSpan, 12 | // }; 13 | use spirv_cross2::Module; 14 | 15 | use super::{ 16 | debug::gl_debug, 17 | gl_object::{NamedObject, NoLateInit, ObjectName}, 18 | }; 19 | //TODO: write more debug logging to compiler_log 20 | #[derive(Debug)] 21 | pub struct Shader { 22 | pub(crate) name: ObjectName, 23 | pub(crate) stage: ShaderType, 24 | pub(crate) refcount: u32, 25 | pub(crate) internal: ShaderInternal, 26 | pub(crate) compiler_log: String, 27 | } 28 | #[derive(Debug, Default)] 29 | pub struct GlslShaderInternal { 30 | pub(crate) source: String, 31 | pub(crate) latest_shader: Option>>, 32 | } 33 | #[derive(Debug)] 34 | pub struct SpirvShaderInternal { 35 | pub(crate) source: Vec, 36 | pub(crate) latest_module: Option>>, 37 | } 38 | #[derive(Debug)] 39 | pub(crate) enum ShaderInternal { 40 | Glsl(GlslShaderInternal), 41 | Spirv(SpirvShaderInternal), 42 | } 43 | impl ShaderInternal { 44 | pub(crate) fn is_spirv(&self) -> bool { 45 | matches!(self, ShaderInternal::Spirv(_)) 46 | } 47 | //4gb shader is not real, 4gb shader cannot hurt you 48 | #[expect(clippy::cast_possible_truncation)] 49 | pub(crate) fn source_len(&self) -> u32 { 50 | (match self { 51 | ShaderInternal::Glsl(internal) => internal.source.len(), 52 | // 4 byte words 53 | ShaderInternal::Spirv(internal) => internal.source.len() * 4, 54 | }) as u32 55 | } 56 | /// Returns whether the last attempted compilation of this internal shader succeeded 57 | pub(crate) fn compile_status(&self) -> bool { 58 | match self { 59 | ShaderInternal::Glsl(glsl_shader_internal) => { 60 | glsl_shader_internal.latest_shader.is_some() 61 | } 62 | ShaderInternal::Spirv(_spirv_shader_internal) => todo!(), 63 | } 64 | } 65 | } 66 | impl Shader { 67 | pub fn new_text_default(name: ObjectName, stage: ShaderType) -> Self { 68 | gl_debug!(src: ShaderCompiler,"created new GLSL {stage:?} {name:?}"); 69 | Self { 70 | name, 71 | stage, 72 | refcount: 0, 73 | internal: ShaderInternal::Glsl(GlslShaderInternal::default()), 74 | compiler_log: String::new(), 75 | } 76 | } 77 | } 78 | impl NamedObject for Shader { 79 | type LateInitType = NoLateInit; 80 | } 81 | 82 | impl Shader { 83 | pub(crate) fn compile(&mut self) { 84 | match &mut self.internal { 85 | ShaderInternal::Glsl(glsl_shader_internal) => { 86 | // Clear the previous compilation attempt 87 | glsl_shader_internal.latest_shader = None; 88 | let source = ShaderSource::from(mem::take(&mut glsl_shader_internal.source)); 89 | let comp = GlslangCompiler::acquire().expect("failed to acquire Glslang compiler"); 90 | 91 | let opts = CompilerOptions { 92 | source_language: SourceLanguage::GLSL, 93 | target: Target::OpenGL { 94 | version: glslang::OpenGlVersion::OpenGL4_5, 95 | spirv_version: Some(glslang::SpirvVersion::SPIRV1_0), 96 | }, 97 | version_profile: None, 98 | messages: ShaderMessage::RELAXED_ERRORS 99 | | ShaderMessage::ENHANCED 100 | | ShaderMessage::DEBUG_INFO 101 | | ShaderMessage::ONLY_PREPROCESSOR 102 | // VULKAN_RULES_RELAXED 103 | | ShaderMessage::from_bits_retain(1 << 2), 104 | }; 105 | 106 | let input = match ShaderInput::new( 107 | &source, 108 | self.stage.to_glslang_stage(), 109 | &opts, 110 | None, 111 | None, 112 | ) { 113 | Ok(input) => input, 114 | Err(err) => { 115 | self.write_to_compiler_log(&err.to_string()); 116 | return; 117 | } 118 | }; 119 | let shader = match comp.create_shader(input) { 120 | Ok(shader) => shader, 121 | Err(e) => { 122 | self.write_to_compiler_log(&e.to_string()); 123 | return; 124 | } 125 | }; 126 | glsl_shader_internal.latest_shader = Some(shader.into()); 127 | } 128 | ShaderInternal::Spirv(_spirv_shader_internal) => todo!(), 129 | } 130 | } 131 | pub(crate) fn write_to_compiler_log(&mut self, info: &str) { 132 | gl_debug!(src: ShaderCompiler, "{:?} compiler log: {info}", self.name); 133 | self.compiler_log.push_str(info); 134 | self.compiler_log.push('\n'); 135 | } 136 | /// Increments the program reference count on this shader. Call this function when attaching a shader object to a program 137 | pub(crate) fn retain(&mut self) { 138 | self.refcount += 1; 139 | } 140 | /// Decrements the program reference count on this shader. Call this function when detaching a shader object from a program. 141 | /// 142 | /// Returns `true` if it is OK to drop this shader object after the decrement (i.e. it is not currently attached to any program objects) 143 | pub(crate) fn release_shader(&mut self) -> bool { 144 | if self.refcount <= 1 { 145 | self.refcount = 0; 146 | true 147 | } else { 148 | self.refcount -= 1; 149 | false 150 | } 151 | } 152 | } 153 | 154 | // TODO correctly detect device capabilities 155 | 156 | impl ShaderType { 157 | #[must_use] 158 | pub fn to_glslang_stage(self) -> ShaderStage { 159 | match self { 160 | ShaderType::FragmentShader => ShaderStage::Fragment, 161 | ShaderType::VertexShader => ShaderStage::Vertex, 162 | //TODO: geometry and tesselation shader emulation :) 163 | ShaderType::GeometryShader => ShaderStage::Geometry, 164 | ShaderType::TessEvaluationShader => ShaderStage::TesselationEvaluation, 165 | ShaderType::TessControlShader => ShaderStage::TesselationControl, 166 | ShaderType::ComputeShader => ShaderStage::Compute, 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/HelloTriangle/hello_colorful_triangle.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Hello colorful triangle 3 | * 4 | * An OpenGL "Hello Triangle" using 5 | * - glad instead of glew (because reason), and glfw3, 6 | - pieces from learnopengl.com 7 | * - the gl_utils lib of Anton Gerdelan tutos 8 | * - DSA, looking at fendevel/Guide-to-Modern-OpenGL-Functions @github 9 | * - heavy comments! This is the way. 10 | * 11 | * @author ludo456 / the opensourcedev @github 12 | */ 13 | 14 | #include "include/gl_utils.h" //includes glad.h & glfw3.h 15 | #include 16 | 17 | // Forward declarations 18 | void framebuffer_size_callback(GLFWwindow *window, int width, int height); 19 | void processInput(GLFWwindow *window); 20 | 21 | int main() { 22 | // glfw: initialize and configure 23 | // ------------------------------ 24 | glfwInit(); 25 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); 26 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); 27 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 28 | 29 | // glfw window creation 30 | // -------------------- 31 | GLFWwindow *window = 32 | glfwCreateWindow(800, 600, "Hello Colorful Triangle", NULL, NULL); 33 | if (window == NULL) { 34 | const char *desc; 35 | glfwGetError(&desc); 36 | std::cout << desc << std::endl; 37 | std::cout << "Failed to create GLFW window" << std::endl; 38 | glfwTerminate(); 39 | return -1; 40 | } 41 | glfwMakeContextCurrent(window); 42 | glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 43 | 44 | // glad: load all OpenGL function pointers 45 | // --------------------------------------- 46 | if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { 47 | std::cout << "Failed to initialize GLAD" << std::endl; 48 | return -1; 49 | } 50 | 51 | // Setting up the triangle data 52 | // ---------------------------- 53 | 54 | float vertices[] = {-1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 55 | 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; 56 | GLuint attribPos = 0; 57 | GLuint attribCol = 1; 58 | 59 | /**************** VBO ***************/ 60 | unsigned int hctVBO; // hello colorful triangle vbo 61 | // glGenBuffers(1, &VBO); // Way to go before openGl 4.5 62 | // glBindBuffer(GL_ARRAY_BUFFER, VBO); // Binding to openGl context was 63 | // necessary 64 | // replaced with: 65 | glCreateBuffers(1, &hctVBO); // uses DSA. This is the way. 66 | 67 | // glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 68 | // replaced with: 69 | glNamedBufferStorage(hctVBO, sizeof(vertices), vertices, 70 | GL_DYNAMIC_STORAGE_BIT); 71 | // ^^^ needed, since there is no context binding. 72 | 73 | /**************** VAO ***************/ 74 | unsigned int hctVAO; 75 | // glGenVertexArrays(1, &VAO); 76 | // glBindVertexArray(VAO); 77 | // replaced with: 78 | glCreateVertexArrays(1, &hctVAO); // This is the way. 79 | 80 | // As there is, by definition, no context binding in DSA, then we need to 81 | //"bind" vao with vbo explicitely, like linking 2 indexes in a database. 82 | 83 | GLuint vaoBindingPoint = 84 | 0; // A binding point in VAO. See GL_MAX_VERTEX_ATTRIB_BINDINGS 85 | glVertexArrayVertexBuffer( 86 | hctVAO, // vao to bind 87 | vaoBindingPoint, // Could be 1, 2... if there were several vbo to source. 88 | hctVBO, // VBO to bound at "vaoBindingPoint". 89 | 0, // offset of the first element in the buffer hctVBO. 90 | 6 * sizeof(float)); // stride == 3 position floats + 3 color floats. 91 | 92 | // glEnableVertexAttribArray(attribPos); 93 | // glEnableVertexAttribArray(attribCol); 94 | // replaced with: 95 | glEnableVertexArrayAttrib(hctVAO, 96 | attribPos); // Need to precise vao, as there is no 97 | // context binding in DSA style 98 | glEnableVertexArrayAttrib( 99 | hctVAO, 100 | attribCol); // Meaning no current vao is bound to the opengl context. 101 | 102 | // glVertexAttribPointer(attribPos, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), 103 | // (void*)0); glVertexAttribPointer(attribCol, 3, GL_FLOAT, GL_FALSE, 6 * 104 | // sizeof(float), (void*)( 3*sizeof(float) )); 105 | // replaced with: 106 | glVertexArrayAttribFormat( 107 | hctVAO, attribPos, 3, GL_FLOAT, false, 108 | 0); // Need to precise vao, as there is no context binding in DSA 109 | glVertexArrayAttribFormat( 110 | hctVAO, attribCol, 3, GL_FLOAT, false, 111 | 3 * sizeof( 112 | float)); // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glVertexAttribFormat.xhtml 113 | 114 | // Explicit binding of an attribute to a vao binding point 115 | glVertexArrayAttribBinding(hctVAO, attribPos, vaoBindingPoint); 116 | glVertexArrayAttribBinding(hctVAO, attribCol, vaoBindingPoint); 117 | 118 | // Create shader using gl_utils 119 | // --------------------------- 120 | GLuint shader_prog = create_programme_from_files("shader_triangle.vert", 121 | "shader_triangle.frag"); 122 | glUseProgram(shader_prog); // Could be copy-pasted in render loop too... 123 | 124 | // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//Wireframe rendering 125 | // render loop 126 | // ----------- 127 | while (!glfwWindowShouldClose(window)) { 128 | // input 129 | // ----- 130 | processInput(window); 131 | glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 10, -1, "Test Group 1"); 132 | glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 10, -1, "Test Group 2"); 133 | // render 134 | // ------ 135 | glClearColor(1.0f, 0.3f, 0.3f, 1.0f); 136 | glClear(GL_COLOR_BUFFER_BIT); 137 | glPopDebugGroup(); 138 | glBindVertexArray(hctVAO); 139 | glDrawArrays(GL_TRIANGLES, 0, 3); 140 | glPopDebugGroup(); 141 | // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved 142 | // etc.) 143 | // ------------------------------------------------------------------------------- 144 | glfwSwapBuffers(window); 145 | glfwPollEvents(); 146 | } 147 | 148 | // glfw: terminate, clearing all previously allocated GLFW resources. 149 | // ------------------------------------------------------------------ 150 | glfwTerminate(); 151 | return 0; 152 | } 153 | 154 | // process all input: query GLFW whether relevant keys are pressed/released this 155 | // frame and react accordingly 156 | // --------------------------------------------------------------------------------------------------------- 157 | void processInput(GLFWwindow *window) { 158 | if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) 159 | glfwSetWindowShouldClose(window, true); 160 | } 161 | 162 | // glfw: whenever the window size changed (by OS or user resize) this callback 163 | // function executes 164 | // --------------------------------------------------------------------------------------------- 165 | void framebuffer_size_callback(GLFWwindow *window, int width, int height) { 166 | // make sure the viewport matches the new window dimensions; note that width 167 | // and height will be significantly larger than specified on retina displays. 168 | glViewport(0, 0, width, height); 169 | } 170 | -------------------------------------------------------------------------------- /tests/HelloTriangle/gl_utils.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | | OpenGL 4 Example Code. | 3 | | Accompanies written series "Anton's OpenGL 4 Tutorials" | 4 | | Email: anton at antongerdelan dot net | 5 | | First version 27 Jan 2014 | 6 | | Dr Anton Gerdelan, Trinity College Dublin, Ireland. | 7 | | See individual libraries separate legal notices | 8 | |******************************************************************************| 9 | | This is just a file holding some commonly-used "utility" functions to keep | 10 | | the main file a bit easier to read. You can might build up something like | 11 | | this as learn more GL. Note that you don't need much code here to do good GL.| 12 | | If you have a big object-oriented engine then maybe you can ask yourself if | 13 | | it is really making life easier. | 14 | \******************************************************************************/ 15 | #include "include/gl_utils.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define GL_LOG_FILE "gl.log" 21 | #define MAX_SHADER_LENGTH 262144 22 | 23 | /*--------------------------------LOG FUNCTIONS-------------------------------*/ 24 | bool restart_gl_log() { 25 | FILE* file = fopen( GL_LOG_FILE, "w" ); 26 | if ( !file ) { 27 | fprintf( stderr, "ERROR: could not open GL_LOG_FILE log file %s for writing\n", GL_LOG_FILE ); 28 | return false; 29 | } 30 | time_t now = time( NULL ); 31 | char* date = ctime( &now ); 32 | fprintf( file, "GL_LOG_FILE log. local time %s\n", date ); 33 | fclose( file ); 34 | return true; 35 | } 36 | 37 | bool gl_log( const char* message, ... ) { 38 | va_list argptr; 39 | FILE* file = fopen( GL_LOG_FILE, "a" ); 40 | if ( !file ) { 41 | fprintf( stderr, "ERROR: could not open GL_LOG_FILE %s file for appending\n", GL_LOG_FILE ); 42 | return false; 43 | } 44 | va_start( argptr, message ); 45 | vfprintf( file, message, argptr ); 46 | va_end( argptr ); 47 | fclose( file ); 48 | return true; 49 | } 50 | 51 | /* same as gl_log except also prints to stderr */ 52 | bool gl_log_err( const char* message, ... ) { 53 | va_list argptr; 54 | FILE* file = fopen( GL_LOG_FILE, "a" ); 55 | if ( !file ) { 56 | fprintf( stderr, "ERROR: could not open GL_LOG_FILE %s file for appending\n", GL_LOG_FILE ); 57 | return false; 58 | } 59 | va_start( argptr, message ); 60 | vfprintf( file, message, argptr ); 61 | va_end( argptr ); 62 | va_start( argptr, message ); 63 | vfprintf( stderr, message, argptr ); 64 | va_end( argptr ); 65 | fclose( file ); 66 | return true; 67 | } 68 | 69 | 70 | void glfw_error_callback( int error, const char* description ) { 71 | fputs( description, stderr ); 72 | gl_log_err( "%s\n", description ); 73 | } 74 | 75 | 76 | void _update_fps_counter( GLFWwindow* window ) { 77 | static double previous_seconds = glfwGetTime(); 78 | static int frame_count; 79 | double current_seconds = glfwGetTime(); 80 | double elapsed_seconds = current_seconds - previous_seconds; 81 | if ( elapsed_seconds > 0.25 ) { 82 | previous_seconds = current_seconds; 83 | double fps = (double)frame_count / elapsed_seconds; 84 | char tmp[128]; 85 | sprintf( tmp, "opengl @ fps: %.2f", fps ); 86 | glfwSetWindowTitle( window, tmp ); 87 | frame_count = 0; 88 | } 89 | frame_count++; 90 | } 91 | 92 | /*-----------------------------------SHADERS----------------------------------*/ 93 | /* copy a shader from a plain text file into a character array */ 94 | bool parse_file_into_str( const char* file_name, char* shader_str, int max_len ) { 95 | FILE* file = fopen( file_name, "r" ); 96 | if ( !file ) { 97 | gl_log_err( "ERROR: opening file for reading: %s\n", file_name ); 98 | return false; 99 | } 100 | size_t cnt = fread( shader_str, 1, max_len - 1, file ); 101 | if ( (int)cnt >= max_len - 1 ) { gl_log_err( "WARNING: file %s too big - truncated.\n", file_name ); } 102 | if ( ferror( file ) ) { 103 | gl_log_err( "ERROR: reading shader file %s\n", file_name ); 104 | fclose( file ); 105 | return false; 106 | } 107 | // append \0 to end of file string 108 | shader_str[cnt] = 0; 109 | fclose( file ); 110 | return true; 111 | } 112 | 113 | void print_shader_info_log( GLuint shader_index ) { 114 | int max_length = 2048; 115 | int actual_length = 0; 116 | char log[2048]; 117 | glGetShaderInfoLog( shader_index, max_length, &actual_length, log ); 118 | printf( "shader info log for GL index %i:\n%s\n", shader_index, log ); 119 | gl_log( "shader info log for GL index %i:\n%s\n", shader_index, log ); 120 | } 121 | 122 | bool create_shader( const char* file_name, GLuint* shader, GLenum type ) { 123 | gl_log( "creating shader from %s...\n", file_name ); 124 | char shader_string[MAX_SHADER_LENGTH]; 125 | parse_file_into_str( file_name, shader_string, MAX_SHADER_LENGTH ); 126 | *shader = glCreateShader( type ); 127 | const GLchar* p = (const GLchar*)shader_string; 128 | glShaderSource( *shader, 1, &p, NULL ); 129 | glCompileShader( *shader ); 130 | // check for compile errors 131 | int params = -1; 132 | glGetShaderiv( *shader, GL_COMPILE_STATUS, ¶ms ); 133 | if ( GL_TRUE != params ) { 134 | gl_log_err( "ERROR: GL shader index %i did not compile\n", *shader ); 135 | print_shader_info_log( *shader ); 136 | return false; // or exit or something 137 | } 138 | gl_log( "shader compiled. index %i\n", *shader ); 139 | return true; 140 | } 141 | 142 | void print_programme_info_log( GLuint sp ) { 143 | int max_length = 2048; 144 | int actual_length = 0; 145 | char log[2048]; 146 | glGetProgramInfoLog( sp, max_length, &actual_length, log ); 147 | printf( "program info log for GL index %u:\n%s", sp, log ); 148 | gl_log( "program info log for GL index %u:\n%s", sp, log ); 149 | } 150 | 151 | bool is_programme_valid( GLuint sp ) { 152 | glValidateProgram( sp ); 153 | GLint params = -1; 154 | glGetProgramiv( sp, GL_VALIDATE_STATUS, ¶ms ); 155 | if ( GL_TRUE != params ) { 156 | gl_log_err( "program %i GL_VALIDATE_STATUS = GL_FALSE\n", sp ); 157 | print_programme_info_log( sp ); 158 | return false; 159 | } 160 | gl_log( "program %i GL_VALIDATE_STATUS = GL_TRUE\n", sp ); 161 | return true; 162 | } 163 | 164 | bool create_programme( GLuint vert, GLuint frag, GLuint* programme ) { 165 | *programme = glCreateProgram(); 166 | gl_log( "created programme %u. attaching shaders %u and %u...\n", *programme, vert, frag ); 167 | glAttachShader( *programme, vert ); 168 | glAttachShader( *programme, frag ); 169 | // link the shader programme. if binding input attributes do that before link 170 | glLinkProgram( *programme ); 171 | GLint params = -1; 172 | glGetProgramiv( *programme, GL_LINK_STATUS, ¶ms ); 173 | if ( GL_TRUE != params ) { 174 | gl_log_err( "ERROR: could not link shader programme GL index %u\n", *programme ); 175 | print_programme_info_log( *programme ); 176 | return false; 177 | } 178 | ( is_programme_valid( *programme ) ); 179 | // delete shaders here to free memory 180 | glDeleteShader( vert ); 181 | glDeleteShader( frag ); 182 | return true; 183 | } 184 | 185 | GLuint create_programme_from_files( const char* vert_file_name, const char* frag_file_name ) { 186 | GLuint vert, frag, programme; 187 | ( create_shader( vert_file_name, &vert, GL_VERTEX_SHADER ) ); 188 | ( create_shader( frag_file_name, &frag, GL_FRAGMENT_SHADER ) ); 189 | ( create_programme( vert, frag, &programme ) ); 190 | return programme; 191 | } 192 | -------------------------------------------------------------------------------- /oxidegl/src/commands/clear.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | context::Context, 3 | gl_enums::ClearBufferMask, 4 | gl_types::{GLdouble, GLfloat, GLint}, 5 | util::run_if_changed, 6 | }; 7 | 8 | impl Context { 9 | /// ### Parameters 10 | /// `mask` 11 | /// 12 | /// > Bitwise OR of masks that indicate the buffers to be cleared. The three 13 | /// > masks are [`GL_COLOR_BUFFER_BIT`](crate::gl_enums::GL_COLOR_BUFFER_BIT), [`GL_DEPTH_BUFFER_BIT`](crate::gl_enums::GL_DEPTH_BUFFER_BIT), 14 | /// > and [`GL_STENCIL_BUFFER_BIT`](crate::gl_enums::GL_STENCIL_BUFFER_BIT). 15 | /// 16 | /// ### Description 17 | /// [**glClear**](crate::context::Context::oxidegl_clear) sets the bitplane 18 | /// area of the window to values previously selected by [**glClearColor**](crate::context::Context::oxidegl_clear_color), 19 | /// [**glClearDepth**](crate::context::Context::oxidegl_clear_depth), and [**glClearStencil**](crate::context::Context::oxidegl_clear_stencil). 20 | /// Multiple color buffers can be cleared simultaneously by selecting more 21 | /// than one buffer at a time using [**glDrawBuffer**](crate::context::Context::oxidegl_draw_buffer). 22 | /// 23 | /// The pixel ownership test, the scissor test, dithering, and the buffer writemasks 24 | /// affect the operation of [**glClear**](crate::context::Context::oxidegl_clear). 25 | /// The scissor box bounds the cleared region. Alpha function, blend function, 26 | /// logical operation, stenciling, texture mapping, and depth-buffering are 27 | /// ignored by [**glClear**](crate::context::Context::oxidegl_clear). 28 | /// 29 | /// [**glClear**](crate::context::Context::oxidegl_clear) takes a single argument 30 | /// that is the bitwise OR of several values indicating which buffer is to 31 | /// be cleared. 32 | /// 33 | /// The values are as follows: 34 | /// 35 | /// [`GL_COLOR_BUFFER_BIT`](crate::gl_enums::GL_COLOR_BUFFER_BIT) 36 | /// 37 | /// > Indicates the buffers currently enabled for color writing. 38 | /// 39 | /// [`GL_DEPTH_BUFFER_BIT`](crate::gl_enums::GL_DEPTH_BUFFER_BIT) 40 | /// 41 | /// > Indicates the depth buffer. 42 | /// 43 | /// [`GL_STENCIL_BUFFER_BIT`](crate::gl_enums::GL_STENCIL_BUFFER_BIT) 44 | /// 45 | /// > Indicates the stencil buffer. 46 | /// 47 | /// The value to which each buffer is cleared depends on the setting of the 48 | /// clear value for that buffer. 49 | /// 50 | /// ### Notes 51 | /// If a buffer is not present, then a [**glClear**](crate::context::Context::oxidegl_clear) 52 | /// directed at that buffer has no effect. 53 | /// 54 | /// ### Associated Gets 55 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_DEPTH_CLEAR_VALUE`](crate::gl_enums::GL_DEPTH_CLEAR_VALUE) 56 | /// 57 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_COLOR_CLEAR_VALUE`](crate::gl_enums::GL_COLOR_CLEAR_VALUE) 58 | /// 59 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_STENCIL_CLEAR_VALUE`](crate::gl_enums::GL_STENCIL_CLEAR_VALUE) 60 | pub fn oxidegl_clear(&mut self, mask: ClearBufferMask) { 61 | run_if_changed!(self.gl_state.clear_values.mask;= mask => self.new_encoder()); 62 | } 63 | 64 | /// ### Parameters 65 | /// `red` 66 | /// 67 | /// `green` 68 | /// 69 | /// `blue` 70 | /// 71 | /// `alpha` 72 | /// 73 | /// > Specify the red, green, blue, and alpha values used when the color buffers 74 | /// > are cleared. The initial values are all 0. 75 | /// 76 | /// ### Description 77 | /// [**glClearColor**](crate::context::Context::oxidegl_clear_color) specifies 78 | /// the red, green, blue, and alpha values used by [**glClear**](crate::context::Context::oxidegl_clear) 79 | /// to clear the color buffers. Values specified by [**glClearColor**](crate::context::Context::oxidegl_clear_color) 80 | /// are clamped to the range `[inlineq]` 81 | /// 82 | /// ### Notes 83 | /// The type of the `red`, `green`, `blue`, and `alpha` parameters was changed 84 | /// from `GLclampf` to `GLfloat`. This change is transparent to user code and is 85 | /// described in detail on the [**removedTypes**](crate::context::Context::oxideremoved_types) 86 | /// page. 87 | /// 88 | /// ### Associated Gets 89 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_COLOR_CLEAR_VALUE`](crate::gl_enums::GL_COLOR_CLEAR_VALUE) 90 | pub fn oxidegl_clear_color( 91 | &mut self, 92 | red: GLfloat, 93 | green: GLfloat, 94 | blue: GLfloat, 95 | alpha: GLfloat, 96 | ) { 97 | run_if_changed!(self.gl_state.clear_values.color;= [red, green, blue, alpha] => self.new_encoder()); 98 | } 99 | /// ### Parameters 100 | /// `s` 101 | /// 102 | /// > Specifies the index used when the stencil buffer is cleared. The initial 103 | /// > value is 0. 104 | /// 105 | /// ### Description 106 | /// [**glClearStencil**](crate::context::Context::oxidegl_clear_stencil) specifies 107 | /// the index used by [**glClear**](crate::context::Context::oxidegl_clear) 108 | /// to clear the stencil buffer. `s` is masked with `[inlineq]` `[inlineq]` 109 | /// 110 | /// ### Associated Gets 111 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_STENCIL_CLEAR_VALUE`](crate::gl_enums::GL_STENCIL_CLEAR_VALUE) 112 | /// 113 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_STENCIL_BITS`](crate::gl_enums::GL_STENCIL_BITS) 114 | #[expect( 115 | clippy::cast_sign_loss, 116 | reason = "we want a bitcast anyways, the numeric value doesnt matter all that much" 117 | )] 118 | pub fn oxidegl_clear_stencil(&mut self, s: GLint) { 119 | run_if_changed!(self.gl_state.clear_values.stencil;= s as u32 => self.new_encoder()); 120 | } 121 | } 122 | 123 | /// ### Parameters 124 | /// `depth` 125 | /// 126 | /// > Specifies the depth value used when the depth buffer is cleared. The initial 127 | /// > value is 1. 128 | /// 129 | /// ### Description 130 | /// [**glClearDepth**](crate::context::Context::oxidegl_clear_depth) specifies 131 | /// the depth value used by [**glClear**](crate::context::Context::oxidegl_clear) 132 | /// to clear the depth buffer. Values specified by [**glClearDepth**](crate::context::Context::oxidegl_clear_depth) 133 | /// are clamped to the range `[inlineq]` 134 | /// 135 | /// ### Notes 136 | /// The type of the `depth` parameter was changed from `GLclampf` to `GLfloat` 137 | /// for [**glClearDepthf**](crate::context::Context::oxidegl_clear_depthf) 138 | /// and from `GLclampd` to `GLdouble` for [**glClearDepth**](crate::context::Context::oxidegl_clear_depth). 139 | /// This change is transparent to user code and is described in detail on 140 | /// the [**removedTypes**](crate::context::Context::oxideremoved_types) page. 141 | /// 142 | /// ### Associated Gets 143 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_DEPTH_CLEAR_VALUE`](crate::gl_enums::GL_DEPTH_CLEAR_VALUE) 144 | #[expect( 145 | clippy::float_cmp, 146 | reason = "if someone sets NaN as the depth buffer clear value they have bigger problems" 147 | )] 148 | impl Context { 149 | // Metal depth buffer is 32 bits so we might as well just truncate here instead of storing an f64 150 | #[allow(clippy::cast_possible_truncation)] 151 | pub fn oxidegl_clear_depth(&mut self, depth: GLdouble) { 152 | run_if_changed!(self.gl_state.clear_values.depth;= depth as f32 => self.new_encoder()); 153 | } 154 | pub fn oxidegl_clear_depthf(&mut self, d: GLfloat) { 155 | run_if_changed!(self.gl_state.clear_values.depth;= d => self.new_encoder()); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /oxidegl/src/error.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::inline_always)] 2 | use std::{convert::Infallible, hint::unreachable_unchecked, mem, panic::Location, ptr}; 3 | 4 | use crate::{ 5 | debug::gl_err, 6 | gl_enums::{ 7 | ErrorCode, GL_INVALID_ENUM, GL_INVALID_FRAMEBUFFER_OPERATION, GL_INVALID_OPERATION, 8 | GL_INVALID_VALUE, GL_OUT_OF_MEMORY, GL_STACK_OVERFLOW, GL_STACK_UNDERFLOW, 9 | }, 10 | gl_types::{GLboolean, GLenum, GLint, GLsync}, 11 | }; 12 | #[expect(clippy::derivable_impls, reason = "avoid modifying generated code")] 13 | impl Default for ErrorCode { 14 | fn default() -> Self { 15 | ErrorCode::NoError 16 | } 17 | } 18 | const ERR_OFFSET: u32 = 1279; 19 | #[repr(u8)] 20 | #[expect(clippy::cast_possible_truncation, reason = "manually checked consts")] 21 | #[allow(unused)] 22 | #[derive(Clone, Copy, Debug)] 23 | // shrink this value to be byte-sized for dubious reasons 24 | pub(crate) enum GlError { 25 | InvalidEnum = (GL_INVALID_ENUM - ERR_OFFSET) as u8, 26 | InvalidValue = (GL_INVALID_VALUE - ERR_OFFSET) as u8, 27 | InvalidOperation = (GL_INVALID_OPERATION - ERR_OFFSET) as u8, 28 | StackOverflow = (GL_STACK_OVERFLOW - ERR_OFFSET) as u8, 29 | StackUnderflow = (GL_STACK_UNDERFLOW - ERR_OFFSET) as u8, 30 | OutOfMemory = (GL_OUT_OF_MEMORY - ERR_OFFSET) as u8, 31 | InvalidFramebufferOperation = (GL_INVALID_FRAMEBUFFER_OPERATION - ERR_OFFSET) as u8, 32 | } 33 | impl GlError { 34 | #[inline] 35 | /// Wraps this [`GlError`] in a [`GlFallibleError`] for use in the [`Err`] variant of [`GlFallible`]. Used to bypass the From impl that logs unhandled errors (which is automatically 36 | /// invoked by [`FromResidual`] when using ? to coalesce [`GlFallible`]) 37 | pub(crate) fn e(self) -> GlFallibleError { 38 | GlFallibleError { 39 | #[cfg(not(feature = "unsound_noerror"))] 40 | err: self, 41 | } 42 | } 43 | } 44 | impl From for ErrorCode { 45 | #[inline] 46 | fn from(value: GlError) -> Self { 47 | // Safety: all variants of GlError correspond to a member discriminant of ErrorCode when added back to ERR_OFFSET 48 | unsafe { mem::transmute(u32::from(value as u8) + ERR_OFFSET) } 49 | } 50 | } 51 | #[derive(Clone, Copy, Debug)] 52 | pub struct GlFallibleError { 53 | #[cfg(not(feature = "unsound_noerror"))] 54 | err: GlError, 55 | } 56 | impl GlFallibleError { 57 | #[inline] 58 | pub(crate) fn get(self) -> ErrorCode { 59 | #[cfg(feature = "unsound_noerror")] 60 | // Safety: user opts into and thus takes responsibility for any potential unsoundess/UB by enabling this feature 61 | unsafe { 62 | std::hint::unreachable_unchecked() 63 | } 64 | #[cfg(not(feature = "unsound_noerror"))] 65 | self.err.into() 66 | } 67 | } 68 | #[cfg(not(feature = "unsound_noerror"))] 69 | impl From for GlFallibleError { 70 | #[inline] 71 | #[cfg_attr(debug_assertions, track_caller)] 72 | fn from(err: GlError) -> Self { 73 | #[cfg(debug_assertions)] 74 | { 75 | let l = Location::caller(); 76 | gl_err!(src: Api, ty: Error, "GlError thrown at {}:{}", l.file(), l.line()); 77 | }; 78 | Self { err } 79 | } 80 | } 81 | #[cfg(feature = "unsound_noerror")] 82 | impl From for GlFallibleError { 83 | #[inline] 84 | fn from(_: GlError) -> Self { 85 | // Safety: user opts into and thus takes responsibility for any potential unsoundess/UB by enabling this feature 86 | unsafe { std::hint::unreachable_unchecked() } 87 | } 88 | } 89 | /// Result of a possibly fallible GL command 90 | pub trait GlResult: Sized { 91 | /// Convert this possibly-fallible value into a `Result` 92 | fn into_result(self) -> Result; 93 | /// Normalize this possibly-fallible value into a `Result`. 94 | /// This function is only used to get the right error type inference in generated code. 95 | /// prefer using [`Self::into_result`](GlResult::into_result) where possible because it preserves 96 | /// the Error type 97 | #[inline] 98 | fn normalize(self) -> GlFallible 99 | where 100 | E: Into, 101 | { 102 | self.into_result().map_err(Into::into) 103 | } 104 | } 105 | 106 | impl GlResult for T { 107 | #[inline] 108 | fn into_result(self) -> Result { 109 | Ok(self) 110 | } 111 | } 112 | impl GlResult for GlFallible { 113 | #[inline] 114 | fn into_result(self) -> Result { 115 | self 116 | } 117 | } 118 | impl From for ErrorCode { 119 | fn from(_: Infallible) -> Self { 120 | // Safety: a value of type Infallible cannot ever exist 121 | unsafe { unreachable_unchecked() } 122 | } 123 | } 124 | impl From for GlFallibleError { 125 | fn from(_: Infallible) -> Self { 126 | // Safety: a value of type Infallible cannot ever exist 127 | unsafe { unreachable_unchecked() } 128 | } 129 | } 130 | impl From for ErrorCode { 131 | fn from(value: GlFallibleError) -> Self { 132 | value.get() 133 | } 134 | } 135 | pub type GlFallible = Result; 136 | 137 | /// Trait that declares the (constant) value to be returned when a GL command that returns a value of type T fails 138 | pub trait GetErrorReturnValue { 139 | #[inline] 140 | #[must_use] 141 | fn get() -> T { 142 | Self::VAL 143 | } 144 | const VAL: T; 145 | } 146 | impl GetErrorReturnValue<()> for GlFallibleError { 147 | const VAL: () = (); 148 | } 149 | impl GetErrorReturnValue for GlFallibleError { 150 | const VAL: GLenum = 0; 151 | } 152 | impl GetErrorReturnValue for GlFallibleError { 153 | const VAL: GLboolean = false; 154 | } 155 | impl GetErrorReturnValue for GlFallibleError { 156 | const VAL: GLint = 0; 157 | } 158 | impl GetErrorReturnValue for GlFallibleError { 159 | const VAL: f32 = 0.0; 160 | } 161 | impl GetErrorReturnValue<*const T> for GlFallibleError { 162 | const VAL: *const T = ptr::null(); 163 | } 164 | impl GetErrorReturnValue<*mut T> for GlFallibleError { 165 | const VAL: *mut T = ptr::null_mut(); 166 | } 167 | impl GetErrorReturnValue for GlFallibleError { 168 | const VAL: GLsync = None; 169 | } 170 | impl GetErrorReturnValue for Infallible { 171 | fn get() -> T { 172 | unreachable!() 173 | } 174 | const VAL: T = panic!("tried to get default/errored return value with error type Infallible"); 175 | } 176 | 177 | macro_rules! gl_assert { 178 | ( $test:expr, $errno:ident ) => { 179 | if !($test) { 180 | crate::debug::gl_err!(src: Api, ty: Error, ::std::concat!(::std::stringify!($errno), " caused by failiure of assertion \"", ::std::stringify!($test), "\"")); 181 | return ::std::result::Result::Err(crate::error::GlError::$errno.e()); 182 | } 183 | }; 184 | ( $test:expr, $errno:ident, $($msg:tt)* ) => { 185 | if !($test) { 186 | crate::debug::gl_err!(src: Api, ty: Error, ::std::concat!(::std::stringify!($errno), " caused by failiure of assertion \"", ::std::stringify!($test), "\": {}"), ::std::format!($($msg)*) ); 187 | return ::std::result::Result::Err(crate::error::GlError::$errno.e()); 188 | } 189 | }; 190 | } 191 | pub(crate) use gl_assert; 192 | 193 | /// Example 194 | /// ```rs 195 | /// error!(InvalidEnum, "this is {}", "an error") 196 | /// ``` 197 | macro_rules! error { 198 | ($errno:ident, $( $rest:tt )*) => { 199 | { 200 | crate::debug::gl_err!(src: Api, ty: Error, $( $rest )* ); 201 | return ::std::result::Result::Err(crate::error::GlError::$errno.e()); 202 | } 203 | } 204 | } 205 | pub(crate) use error; 206 | -------------------------------------------------------------------------------- /tests/HelloTriangle/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | /* 157 | * To support platform where unsigned long cannot be used interchangeably with 158 | * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. 159 | * Ideally, we could just use (u)intptr_t everywhere, but this could result in 160 | * ABI breakage if khronos_uintptr_t is changed from unsigned long to 161 | * unsigned long long or similar (this results in different C++ name mangling). 162 | * To avoid changes for existing platforms, we restrict usage of intptr_t to 163 | * platforms where the size of a pointer is larger than the size of long. 164 | */ 165 | #if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) 166 | #if __SIZEOF_POINTER__ > __SIZEOF_LONG__ 167 | #define KHRONOS_USE_INTPTR_T 168 | #endif 169 | #endif 170 | 171 | #elif defined(__VMS ) || defined(__sgi) 172 | 173 | /* 174 | * Using 175 | */ 176 | #include 177 | typedef int32_t khronos_int32_t; 178 | typedef uint32_t khronos_uint32_t; 179 | typedef int64_t khronos_int64_t; 180 | typedef uint64_t khronos_uint64_t; 181 | #define KHRONOS_SUPPORT_INT64 1 182 | #define KHRONOS_SUPPORT_FLOAT 1 183 | 184 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 185 | 186 | /* 187 | * Win32 188 | */ 189 | typedef __int32 khronos_int32_t; 190 | typedef unsigned __int32 khronos_uint32_t; 191 | typedef __int64 khronos_int64_t; 192 | typedef unsigned __int64 khronos_uint64_t; 193 | #define KHRONOS_SUPPORT_INT64 1 194 | #define KHRONOS_SUPPORT_FLOAT 1 195 | 196 | #elif defined(__sun__) || defined(__digital__) 197 | 198 | /* 199 | * Sun or Digital 200 | */ 201 | typedef int khronos_int32_t; 202 | typedef unsigned int khronos_uint32_t; 203 | #if defined(__arch64__) || defined(_LP64) 204 | typedef long int khronos_int64_t; 205 | typedef unsigned long int khronos_uint64_t; 206 | #else 207 | typedef long long int khronos_int64_t; 208 | typedef unsigned long long int khronos_uint64_t; 209 | #endif /* __arch64__ */ 210 | #define KHRONOS_SUPPORT_INT64 1 211 | #define KHRONOS_SUPPORT_FLOAT 1 212 | 213 | #elif 0 214 | 215 | /* 216 | * Hypothetical platform with no float or int64 support 217 | */ 218 | typedef int khronos_int32_t; 219 | typedef unsigned int khronos_uint32_t; 220 | #define KHRONOS_SUPPORT_INT64 0 221 | #define KHRONOS_SUPPORT_FLOAT 0 222 | 223 | #else 224 | 225 | /* 226 | * Generic fallback 227 | */ 228 | #include 229 | typedef int32_t khronos_int32_t; 230 | typedef uint32_t khronos_uint32_t; 231 | typedef int64_t khronos_int64_t; 232 | typedef uint64_t khronos_uint64_t; 233 | #define KHRONOS_SUPPORT_INT64 1 234 | #define KHRONOS_SUPPORT_FLOAT 1 235 | 236 | #endif 237 | 238 | 239 | /* 240 | * Types that are (so far) the same on all platforms 241 | */ 242 | typedef signed char khronos_int8_t; 243 | typedef unsigned char khronos_uint8_t; 244 | typedef signed short int khronos_int16_t; 245 | typedef unsigned short int khronos_uint16_t; 246 | 247 | /* 248 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 249 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 250 | * to be the only LLP64 architecture in current use. 251 | */ 252 | #ifdef KHRONOS_USE_INTPTR_T 253 | typedef intptr_t khronos_intptr_t; 254 | typedef uintptr_t khronos_uintptr_t; 255 | #elif defined(_WIN64) 256 | typedef signed long long int khronos_intptr_t; 257 | typedef unsigned long long int khronos_uintptr_t; 258 | #else 259 | typedef signed long int khronos_intptr_t; 260 | typedef unsigned long int khronos_uintptr_t; 261 | #endif 262 | 263 | #if defined(_WIN64) 264 | typedef signed long long int khronos_ssize_t; 265 | typedef unsigned long long int khronos_usize_t; 266 | #else 267 | typedef signed long int khronos_ssize_t; 268 | typedef unsigned long int khronos_usize_t; 269 | #endif 270 | 271 | #if KHRONOS_SUPPORT_FLOAT 272 | /* 273 | * Float type 274 | */ 275 | typedef float khronos_float_t; 276 | #endif 277 | 278 | #if KHRONOS_SUPPORT_INT64 279 | /* Time types 280 | * 281 | * These types can be used to represent a time interval in nanoseconds or 282 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 283 | * of nanoseconds since some arbitrary system event (e.g. since the last 284 | * time the system booted). The Unadjusted System Time is an unsigned 285 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 286 | * may be either signed or unsigned. 287 | */ 288 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 289 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 290 | #endif 291 | 292 | /* 293 | * Dummy value used to pad enum types to 32 bits. 294 | */ 295 | #ifndef KHRONOS_MAX_ENUM 296 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 297 | #endif 298 | 299 | /* 300 | * Enumerated boolean type 301 | * 302 | * Values other than zero should be considered to be true. Therefore 303 | * comparisons should not be made against KHRONOS_TRUE. 304 | */ 305 | typedef enum { 306 | KHRONOS_FALSE = 0, 307 | KHRONOS_TRUE = 1, 308 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 309 | } khronos_boolean_enum_t; 310 | 311 | #endif /* __khrplatform_h_ */ 312 | -------------------------------------------------------------------------------- /xtask/src/doc_parse.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use const_format::concatcp; 4 | use roxmltree::{Children, Document, Node, ParsingOptions}; 5 | 6 | use crate::{ 7 | codegen::{CONTEXT_STRUCT_PATH, ENUMS_PATH}, 8 | snake_case_from_title_case, NodeExt, 9 | }; 10 | 11 | #[derive(Debug)] 12 | pub struct RefPageEntry { 13 | pub funcs: Vec, 14 | pub doc: String, 15 | } 16 | pub fn get_refpage_entry<'a>(reg: &'a Document<'a>) -> RefPageEntry { 17 | let mut builder = MarkdownDocCommentBuilder::new(); 18 | let mut funcs = Vec::with_capacity(1); 19 | let rec: Children<'a, '_> = reg.root_element().children(); 20 | for node in rec { 21 | if let Some(id) = node.find_named_attribute("id") { 22 | match id.value() { 23 | "parameters" => { 24 | builder.write_heading("Parameters"); 25 | docbook_to_markdown(&node, &mut builder); 26 | } 27 | "description" => { 28 | builder.write_heading("Description"); 29 | docbook_to_markdown(&node, &mut builder); 30 | } 31 | "notes" => { 32 | builder.write_heading("Notes"); 33 | docbook_to_markdown(&node, &mut builder); 34 | } 35 | "associatedgets" => { 36 | builder.write_heading("Associated Gets"); 37 | docbook_to_markdown(&node, &mut builder); 38 | } 39 | _ => {} 40 | } 41 | } 42 | if node.tag_name().name() == "refsynopsisdiv" { 43 | for synopsis in node 44 | .children() 45 | .filter(|n| n.tag_name().name() == "funcsynopsis") 46 | { 47 | for prototype in synopsis 48 | .children() 49 | .filter(|n| n.tag_name().name() == "funcprototype") 50 | { 51 | let name = prototype 52 | .find_named_child("funcdef") 53 | .unwrap() 54 | .find_named_child("function") 55 | .unwrap() 56 | .text() 57 | .unwrap(); 58 | funcs.push(name.to_string()) 59 | } 60 | } 61 | } 62 | } 63 | builder 64 | .backing_string 65 | .truncate(builder.backing_string.len() - 4); 66 | RefPageEntry { 67 | funcs, 68 | doc: builder.backing_string, 69 | } 70 | } 71 | 72 | pub const CONTEXT_ASSOCFUNC_PATH: &str = concatcp!(CONTEXT_STRUCT_PATH, "::"); 73 | 74 | fn docbook_to_markdown_children<'a>( 75 | node: &'a Node<'a, '_>, 76 | builder: &mut MarkdownDocCommentBuilder, 77 | ) { 78 | for n in node.children() { 79 | docbook_to_markdown(&n, builder); 80 | } 81 | } 82 | /// Writes Docbook XML to the given MarkdownDocCommentBuilder 83 | fn docbook_to_markdown<'a>(node: &'a Node<'a, '_>, builder: &mut MarkdownDocCommentBuilder) { 84 | match node.tag_name().name() { 85 | "para" => { 86 | builder.write_to_body_escaping(node.text().unwrap_or("")); 87 | docbook_to_markdown_children(node, builder); 88 | builder.line_break(); 89 | } 90 | "term" => { 91 | builder.write_to_body_escaping(node.text().unwrap_or("")); 92 | docbook_to_markdown_children(node, builder); 93 | builder.line_break(); 94 | } 95 | "listitem" => { 96 | builder.indent(); 97 | builder.write_to_body_escaping(node.text().unwrap_or("")); 98 | docbook_to_markdown_children(node, builder); 99 | builder.unindent(); 100 | } 101 | "include" => { 102 | let filename = node.find_named_attribute("href").unwrap().value().trim(); 103 | let src = std::fs::read_to_string( 104 | PathBuf::from("reference/OpenGL-Refpages/gl4").join(filename), 105 | ) 106 | .unwrap(); 107 | let opts = ParsingOptions { 108 | allow_dtd: true, 109 | nodes_limit: u32::MAX, 110 | }; 111 | let d = Document::parse_with_options(&src, opts).unwrap(); 112 | docbook_to_markdown(&d.root(), builder); 113 | } 114 | "informaltable" | "table" => { 115 | write_informaltable(node, builder); 116 | } 117 | 118 | "constant" => { 119 | if let Some(t) = node.text() { 120 | builder.write_to_body(&format!("[`{}`]({}{})", t, ENUMS_PATH, t)); 121 | } 122 | builder.write_to_body_escaping(node.tail().unwrap_or("")); 123 | } 124 | "parameter" => { 125 | if let Some(s) = node.text() { 126 | builder.write_to_body(&format!("`{}`", s)); 127 | } 128 | builder.write_to_body_escaping(node.tail().unwrap_or("")); 129 | } 130 | "emphasis" => { 131 | builder.write_to_body(&format!("*{}*", node.text().unwrap_or(""))); 132 | builder.write_to_body_escaping(node.tail().unwrap_or("")); 133 | } 134 | "function" => { 135 | let funcname = node.text().unwrap_or(""); 136 | builder.write_to_body(&format!( 137 | "[**{funcname}**]({CONTEXT_ASSOCFUNC_PATH}oxide{})", 138 | snake_case_from_title_case(funcname) 139 | )); 140 | 141 | builder.write_to_body_escaping(node.tail().unwrap_or("")) 142 | } 143 | "citerefentry" => { 144 | let funcname = node 145 | .find_named_child("refentrytitle") 146 | .unwrap() 147 | .text() 148 | .unwrap(); 149 | builder.write_to_body(&format!( 150 | "[**{funcname}**]({CONTEXT_ASSOCFUNC_PATH}oxide{})", 151 | snake_case_from_title_case(funcname) 152 | )); 153 | 154 | builder.write_to_body_escaping(node.tail().unwrap_or("")) 155 | } 156 | "title" => {} 157 | "inlineequation" => { 158 | builder.write_to_body("`[inlineq]`"); 159 | } 160 | "entry" => { 161 | builder.write_to_body(node.text().unwrap_or("")); 162 | docbook_to_markdown_children(node, builder); 163 | } 164 | _ => { 165 | docbook_to_markdown_children(node, builder); 166 | } 167 | } 168 | } 169 | 170 | pub struct MarkdownDocCommentBuilder { 171 | current_prefix: String, 172 | backing_string: String, 173 | current_line_len: usize, 174 | wrapping: bool, 175 | allow_line_break: bool, 176 | } 177 | impl MarkdownDocCommentBuilder { 178 | pub fn new() -> Self { 179 | Self { 180 | current_prefix: "".to_owned(), 181 | backing_string: String::new(), 182 | current_line_len: 0, 183 | wrapping: true, 184 | allow_line_break: true, 185 | } 186 | } 187 | pub fn write_line(&mut self, line: &str) { 188 | self.current_line_len = 0; 189 | self.write_line_header(); 190 | self.backing_string.push_str(line); 191 | self.backing_string.push('\n'); 192 | } 193 | pub fn write_to_body_escaping(&mut self, to_write: &str) { 194 | let s = to_write 195 | .chars() 196 | .flat_map(|c| { 197 | if matches!(c, '[' | ']' | '|') { 198 | vec!['\\', c] 199 | } else { 200 | vec![c] 201 | } 202 | }) 203 | .collect::(); 204 | self.write_to_body(&s); 205 | } 206 | pub fn push_char(&mut self, c: char) { 207 | self.current_line_len += 1; 208 | self.backing_string.push(c) 209 | } 210 | pub fn write_to_body(&mut self, to_write: &str) { 211 | let it = to_write.split_whitespace().collect::>(); 212 | let count = it.len(); 213 | if count == 0 { 214 | return; 215 | } 216 | let f = *it.first().unwrap(); 217 | let mut iter = it.into_iter().enumerate(); 218 | if self.current_line_len == 0 { 219 | if let Some(c) = f.chars().next() { 220 | if c.is_ascii_punctuation() && f.len() == 1 { 221 | self.backing_string.push(c); 222 | if count == 1 { 223 | return; 224 | } 225 | iter.next(); 226 | self.current_line_len += 1; 227 | } 228 | } 229 | self.write_line_header(); 230 | self.backing_string.pop(); 231 | } 232 | 233 | let elen = count - 1; 234 | for (idx, word) in iter { 235 | if let Some(c) = word.chars().next() { 236 | if c.is_ascii_punctuation() && word.len() == 1 { 237 | self.backing_string.push(c); 238 | self.current_line_len += 1; 239 | continue; 240 | } 241 | } 242 | 243 | self.backing_string.push(' '); 244 | self.backing_string.push_str(word); 245 | self.current_line_len += word.len() + 1; 246 | 247 | if self.current_line_len > 70 && self.wrapping { 248 | self.backing_string.push('\n'); 249 | if idx != elen { 250 | self.write_line_header(); 251 | } 252 | //discard trailing space 253 | self.backing_string.pop(); 254 | self.current_line_len = 0; 255 | } 256 | } 257 | } 258 | 259 | fn write_line_header(&mut self) { 260 | if !self.backing_string.ends_with('\n') && !self.backing_string.is_empty() { 261 | self.backing_string.push('\n'); 262 | } 263 | self.backing_string.push_str("/// "); 264 | self.backing_string.push_str(&self.current_prefix); 265 | if !self.current_prefix.is_empty() { 266 | self.backing_string.push(' '); 267 | } 268 | } 269 | pub fn line_break(&mut self) { 270 | if self.allow_line_break { 271 | if !self.backing_string.ends_with('\n') { 272 | self.backing_string.push('\n'); 273 | } 274 | self.current_line_len = 0; 275 | self.backing_string.push_str("///\n"); 276 | } else { 277 | self.push_char(';'); 278 | self.push_char(' '); 279 | } 280 | } 281 | pub fn write_heading(&mut self, header: &str) { 282 | self.write_line_header(); 283 | self.backing_string.push_str("### "); 284 | self.backing_string.push_str(header); 285 | self.backing_string.push('\n'); 286 | } 287 | pub fn indent(&mut self) { 288 | self.current_prefix.push('>'); 289 | } 290 | pub fn unindent(&mut self) { 291 | if self.current_prefix.ends_with('>') { 292 | self.current_prefix.pop(); 293 | } 294 | } 295 | pub fn prefix_none(&mut self) { 296 | self.current_prefix.clear(); 297 | } 298 | pub fn prefix_bulletted_list(&mut self) { 299 | self.current_prefix.push('*') 300 | } 301 | } 302 | 303 | impl Default for MarkdownDocCommentBuilder { 304 | fn default() -> Self { 305 | Self::new() 306 | } 307 | } 308 | fn write_informaltable<'a>(node: &'a Node<'a, '_>, builder: &mut MarkdownDocCommentBuilder) { 309 | let tgroup = node.find_named_child("tgroup").unwrap(); 310 | let thead = tgroup.find_named_child("thead").unwrap(); 311 | builder.wrapping = false; 312 | let mut widths = Vec::new(); 313 | let row = thead 314 | .children() 315 | .filter(|n| n.has_children()) 316 | .next_back() 317 | .unwrap(); 318 | builder.write_line_header(); 319 | builder.backing_string.push('|'); 320 | for entry in row.children().filter(|n| { 321 | n.has_children() 322 | | (n.text().is_some() 323 | && n.text().map(|v| v.split_whitespace().count() > 0) == Some(true)) 324 | }) { 325 | let start_len = builder.current_line_len; 326 | builder.current_line_len += 1; 327 | docbook_to_markdown(&entry, builder); 328 | let w = builder.current_line_len - start_len; 329 | for _ in 0..(w / 2 + if w > 15 { 18 } else { 0 }) { 330 | builder.push_char(' '); 331 | } 332 | 333 | builder.current_line_len -= 1; 334 | builder.push_char(' '); 335 | builder.push_char('|'); 336 | 337 | widths.push(builder.current_line_len - start_len); 338 | } 339 | widths.iter_mut().for_each(|v| *v -= 1); 340 | builder.write_line_header(); 341 | builder.write_to_body("|"); 342 | for width in widths.iter() { 343 | builder.backing_string.push_str(&"-".repeat(*width)); 344 | builder.backing_string.push('|'); 345 | } 346 | 347 | for row in tgroup 348 | .find_named_child("tbody") 349 | .unwrap() 350 | .children() 351 | .filter(|c| c.has_children()) 352 | { 353 | builder.write_line_header(); 354 | builder.current_line_len = 0; 355 | builder.push_char('|'); 356 | 357 | let mut children_iter = row.children().filter(|n| n.tag_name().name() == "entry"); 358 | 359 | let mut target_width = 1; 360 | let slen = builder.backing_string.len(); 361 | 362 | for width in widths.iter() { 363 | target_width += *width + 1; 364 | 365 | if let Some(entry) = children_iter.next() { 366 | builder.allow_line_break = false; 367 | docbook_to_markdown(&entry, builder); 368 | builder.allow_line_break = true; 369 | } 370 | 371 | let len = (builder.backing_string.len() - slen) + 3; 372 | if len < target_width { 373 | for _ in 0..(target_width - len) { 374 | builder.push_char(' '); 375 | } 376 | } 377 | builder.push_char(' '); 378 | builder.push_char('|'); 379 | } 380 | } 381 | 382 | builder.wrapping = true; 383 | builder.line_break(); 384 | } 385 | -------------------------------------------------------------------------------- /oxidegl/src/commands/misc.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | context::{Context, state::PixelAlignedRect}, 3 | debug::gl_debug, 4 | error::{GlFallible, gl_assert}, 5 | gl_enums::ErrorCode, 6 | gl_types::{GLenum, GLfloat, GLint, GLsizei, GLuint}, 7 | util::run_if_changed, 8 | }; 9 | 10 | impl Context { 11 | // glScissor --------------- 12 | 13 | /// ### Parameters 14 | /// `x` 15 | /// 16 | /// `y` 17 | /// 18 | /// > Specify the lower left corner of the scissor box. Initially (0, 0). 19 | /// 20 | /// `width` 21 | /// 22 | /// `height` 23 | /// 24 | /// > Specify the width and height of the scissor box. When a GL context is first 25 | /// > attached to a window, `width` and `height` are set to the dimensions of 26 | /// > that window. 27 | /// 28 | /// ### Description 29 | /// [**glScissor**](crate::context::Context::oxidegl_scissor) defines a rectangle, 30 | /// called the scissor box, in window coordinates. The first two arguments, 31 | /// `x` and `y`, specify the lower left corner of the box. `width` and `height` 32 | /// specify the width and height of the box. 33 | /// 34 | /// To enable and disable the scissor test, call [**glEnable**](crate::context::Context::oxidegl_enable) 35 | /// and [**glDisable**](crate::context::Context::oxidegl_disable) with argument 36 | /// [`GL_SCISSOR_TEST`](crate::gl_enums::GL_SCISSOR_TEST). The test is initially 37 | /// disabled. While the test is enabled, only pixels that lie within the scissor 38 | /// box can be modified by drawing commands. Window coordinates have integer 39 | /// values at the shared corners of frame buffer pixels. 40 | /// 41 | /// When the scissor test is disabled, it is as though the scissor box includes 42 | /// the entire window. 43 | /// 44 | /// ### Associated Gets 45 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_SCISSOR_BOX`](crate::gl_enums::GL_SCISSOR_BOX) 46 | /// 47 | /// [**glIsEnabled**](crate::context::Context::oxidegl_is_enabled) with argument 48 | /// [`GL_SCISSOR_TEST`](crate::gl_enums::GL_SCISSOR_TEST) 49 | pub fn oxidegl_scissor(&mut self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) { 50 | panic!("command oxidegl_scissor not yet implemented"); 51 | } 52 | 53 | /// ### Parameters 54 | /// `first` 55 | /// 56 | /// > Specifies the index of the first viewport whose scissor box to modify. 57 | /// 58 | /// `count` 59 | /// 60 | /// > Specifies the number of scissor boxes to modify. 61 | /// 62 | /// `v` 63 | /// 64 | /// > Specifies the address of an array containing the left, bottom, width and 65 | /// > height of each scissor box, in that order. 66 | /// 67 | /// ### Description 68 | /// [**glScissorArrayv**](crate::context::Context::oxidegl_scissor_arrayv) 69 | /// defines rectangles, called scissor boxes, in window coordinates for each 70 | /// viewport. `first` specifies the index of the first scissor box to modify 71 | /// and `count` specifies the number of scissor boxes to modify. `first` must 72 | /// be less than the value of [`GL_MAX_VIEWPORTS`](crate::gl_enums::GL_MAX_VIEWPORTS), 73 | /// and `first`+ `count` must be less than or equal to the value of [`GL_MAX_VIEWPORTS`](crate::gl_enums::GL_MAX_VIEWPORTS). 74 | /// `v` specifies the address of an array containing integers specifying the 75 | /// lower left corner of the scissor boxes, and the width and height of the 76 | /// scissor boxes, in that order. 77 | /// 78 | /// To enable and disable the scissor test, call [**glEnable**](crate::context::Context::oxidegl_enable) 79 | /// and [**glDisable**](crate::context::Context::oxidegl_disable) with argument 80 | /// [`GL_SCISSOR_TEST`](crate::gl_enums::GL_SCISSOR_TEST). The test is initially 81 | /// disabled for all viewports. While the test is enabled, only pixels that 82 | /// lie within the scissor box can be modified by drawing commands. Window 83 | /// coordinates have integer values at the shared corners of frame buffer pixels. 84 | /// 85 | /// When the scissor test is disabled, it is as though the scissor box includes 86 | /// the entire window. 87 | /// 88 | /// ### Associated Gets 89 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_SCISSOR_BOX`](crate::gl_enums::GL_SCISSOR_BOX) 90 | /// 91 | /// [**glIsEnabled**](crate::context::Context::oxidegl_is_enabled) with argument 92 | /// [`GL_SCISSOR_TEST`](crate::gl_enums::GL_SCISSOR_TEST) 93 | pub unsafe fn oxidegl_scissor_arrayv( 94 | &mut self, 95 | first: GLuint, 96 | count: GLsizei, 97 | v: *const GLint, 98 | ) { 99 | panic!("command oxidegl_scissor_arrayv not yet implemented"); 100 | } 101 | 102 | // glViewport --------------- 103 | 104 | /// ### Parameters 105 | /// `x` 106 | /// 107 | /// `y` 108 | /// 109 | /// > Specify the lower left corner of the viewport rectangle, in pixels. The 110 | /// > initial value is (0,0). 111 | /// 112 | /// `width` 113 | /// 114 | /// `height` 115 | /// 116 | /// > Specify the width and height of the viewport. When a GL context is first 117 | /// > attached to a window, `width` and `height` are set to the dimensions of 118 | /// > that window. 119 | /// 120 | /// ### Description 121 | /// [**glViewport**](crate::context::Context::oxidegl_viewport) specifies the 122 | /// affine transformation of `[inlineq]` `[inlineq]` `[inlineq]` `[inlineq]` 123 | /// 124 | /// 125 | /// 126 | /// Viewport width and height are silently clamped to a range that depends 127 | /// on the implementation. To query this range, call [**glGet**](crate::context::Context::oxidegl_get) 128 | /// with argument [`GL_MAX_VIEWPORT_DIMS`](crate::gl_enums::GL_MAX_VIEWPORT_DIMS). 129 | /// 130 | /// ### Associated Gets 131 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_VIEWPORT`](crate::gl_enums::GL_VIEWPORT) 132 | /// 133 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_MAX_VIEWPORT_DIMS`](crate::gl_enums::GL_MAX_VIEWPORT_DIMS) 134 | #[expect(clippy::cast_sign_loss)] 135 | pub fn oxidegl_viewport( 136 | &mut self, 137 | x: GLint, 138 | y: GLint, 139 | width: GLsizei, 140 | height: GLsizei, 141 | ) -> GlFallible { 142 | gl_debug!("glViewport, x {x} y {y} width {width} height {height}"); 143 | gl_assert!(width >= 0 && height >= 0, InvalidValue); 144 | debug_assert!(x >= 0 && y >= 0, "negative base coordinate in glViewport"); 145 | 146 | run_if_changed!(self.gl_state.viewport;= PixelAlignedRect { 147 | x: x as u32, 148 | y: y as u32, 149 | width: width as u32, 150 | height: height as u32, 151 | } => self.update_encoder()); 152 | Ok(()) 153 | } 154 | /// ### Parameters 155 | /// `first` 156 | /// 157 | /// > Specify the first viewport to set. 158 | /// 159 | /// `count` 160 | /// 161 | /// > Specify the number of viewports to set. 162 | /// 163 | /// `v` 164 | /// 165 | /// > Specify the address of an array containing the viewport parameters. 166 | /// 167 | /// ### Description 168 | /// [**glViewportArrayv**](crate::context::Context::oxidegl_viewport_arrayv) 169 | /// specifies the parameters for multiple viewports simulataneously. `first` 170 | /// specifies the index of the first viewport to modify and `count` specifies 171 | /// the number of viewports to modify. `first` must be less than the value 172 | /// of [`GL_MAX_VIEWPORTS`](crate::gl_enums::GL_MAX_VIEWPORTS), and `first`+ `count` 173 | /// must be less than or equal to the value of [`GL_MAX_VIEWPORTS`](crate::gl_enums::GL_MAX_VIEWPORTS). 174 | /// Viewports whose indices lie outside the range \[ `first`, `first`+ `count`) 175 | /// are not modified. `v` contains the address of an array of floating point 176 | /// values specifying the left( `[inlineq]` `[inlineq]` `[inlineq]` `[inlineq]` 177 | /// `[inlineq]` `[inlineq]` `[inlineq]` `[inlineq]` `[inlineq]` `[inlineq]` 178 | /// `[inlineq]` `[inlineq]` 179 | /// 180 | /// 181 | /// 182 | /// The location of the viewport's bottom left corner, given by( `[inlineq]` 183 | /// `[inlineq]` `[inlineq]` `[inlineq]` [**glGet**](crate::context::Context::oxidegl_get) 184 | /// with argument [`GL_VIEWPORT_BOUNDS_RANGE`](crate::gl_enums::GL_VIEWPORT_BOUNDS_RANGE). 185 | /// Viewport width and height are silently clamped to a range that depends 186 | /// on the implementation. To query this range, call [**glGet**](crate::context::Context::oxidegl_get) 187 | /// with argument [`GL_MAX_VIEWPORT_DIMS`](crate::gl_enums::GL_MAX_VIEWPORT_DIMS). 188 | /// 189 | /// The precision with which the GL interprets the floating point viewport 190 | /// bounds is implementation-dependent and may be determined by querying the 191 | /// impementation-defined constant [`GL_VIEWPORT_SUBPIXEL_BITS`](crate::gl_enums::GL_VIEWPORT_SUBPIXEL_BITS). 192 | /// 193 | /// ### Associated Gets 194 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_VIEWPORT`](crate::gl_enums::GL_VIEWPORT) 195 | /// 196 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_MAX_VIEWPORT_DIMS`](crate::gl_enums::GL_MAX_VIEWPORT_DIMS) 197 | /// 198 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_VIEWPORT_BOUNDS_RANGE`](crate::gl_enums::GL_VIEWPORT_BOUNDS_RANGE) 199 | /// 200 | /// [**glGet**](crate::context::Context::oxidegl_get) with argument [`GL_VIEWPORT_SUBPIXEL_BITS`](crate::gl_enums::GL_VIEWPORT_SUBPIXEL_BITS) 201 | pub unsafe fn oxidegl_viewport_arrayv( 202 | &mut self, 203 | first: GLuint, 204 | count: GLsizei, 205 | v: *const GLfloat, 206 | ) { 207 | panic!("command oxidegl_viewport_arrayv not yet implemented"); 208 | } 209 | /// ### Description 210 | /// [**glGetError**](crate::context::Context::oxidegl_get_error) returns the 211 | /// value of the error flag. Each detectable error is assigned a numeric code 212 | /// and symbolic name. When an error occurs, the error flag is set to the appropriate 213 | /// error code value. No other errors are recorded until [**glGetError**](crate::context::Context::oxidegl_get_error) 214 | /// is called, the error code is returned, and the flag is reset to [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR). 215 | /// If a call to [**glGetError**](crate::context::Context::oxidegl_get_error) 216 | /// returns [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR), there has been no detectable 217 | /// error since the last call to [**glGetError**](crate::context::Context::oxidegl_get_error), 218 | /// or since the GL was initialized. 219 | /// 220 | /// To allow for distributed implementations, there may be several error flags. 221 | /// If any single error flag has recorded an error, the value of that flag 222 | /// is returned and that flag is reset to [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR) 223 | /// when [**glGetError**](crate::context::Context::oxidegl_get_error) is called. 224 | /// If more than one flag has recorded an error, [**glGetError**](crate::context::Context::oxidegl_get_error) 225 | /// returns and clears an arbitrary error flag value. Thus, [**glGetError**](crate::context::Context::oxidegl_get_error) 226 | /// should always be called in a loop, until it returns [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR), 227 | /// if all error flags are to be reset. 228 | /// 229 | /// Initially, all error flags are set to [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR). 230 | /// 231 | /// The following errors are currently defined: 232 | /// 233 | /// [`GL_NO_ERROR`](crate::gl_enums::GL_NO_ERROR) 234 | /// 235 | /// > No error has been recorded. The value of this symbolic constant is guaranteed 236 | /// > to be 0. 237 | /// 238 | /// [`GL_INVALID_ENUM`](crate::gl_enums::GL_INVALID_ENUM) 239 | /// 240 | /// > An unacceptable value is specified for an enumerated argument. The offending 241 | /// > command is ignored and has no other side effect than to set the error flag. 242 | /// 243 | /// [`GL_INVALID_VALUE`](crate::gl_enums::GL_INVALID_VALUE) 244 | /// 245 | /// > A numeric argument is out of range. The offending command is ignored and 246 | /// > has no other side effect than to set the error flag. 247 | /// 248 | /// [`GL_INVALID_OPERATION`](crate::gl_enums::GL_INVALID_OPERATION) 249 | /// 250 | /// > The specified operation is not allowed in the current state. The offending 251 | /// > command is ignored and has no other side effect than to set the error flag. 252 | /// 253 | /// [`GL_INVALID_FRAMEBUFFER_OPERATION`](crate::gl_enums::GL_INVALID_FRAMEBUFFER_OPERATION) 254 | /// 255 | /// > The framebuffer object is not complete. The offending command is ignored 256 | /// > and has no other side effect than to set the error flag. 257 | /// 258 | /// [`GL_OUT_OF_MEMORY`](crate::gl_enums::GL_OUT_OF_MEMORY) 259 | /// 260 | /// > There is not enough memory left to execute the command. The state of the 261 | /// > GL is undefined, except for the state of the error flags, after this error 262 | /// > is recorded. 263 | /// 264 | /// [`GL_STACK_UNDERFLOW`](crate::gl_enums::GL_STACK_UNDERFLOW) 265 | /// 266 | /// > An attempt has been made to perform an operation that would cause an internal 267 | /// > stack to underflow. 268 | /// 269 | /// [`GL_STACK_OVERFLOW`](crate::gl_enums::GL_STACK_OVERFLOW) 270 | /// 271 | /// > An attempt has been made to perform an operation that would cause an internal 272 | /// > stack to overflow. 273 | /// 274 | /// When an error flag is set, results of a GL operation are undefined only 275 | /// if [`GL_OUT_OF_MEMORY`](crate::gl_enums::GL_OUT_OF_MEMORY) has occurred. In 276 | /// all other cases, the command generating the error is ignored and has no 277 | /// effect on the GL state or frame buffer contents. If the generating command 278 | /// returns a value, it returns 0. If [**glGetError**](crate::context::Context::oxidegl_get_error) 279 | /// itself generates an error, it returns 0. 280 | pub fn oxidegl_get_error(&mut self) -> GLenum { 281 | let r = self.gl_state.error; 282 | self.gl_state.error = ErrorCode::NoError; 283 | r.into() 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /oxidegl/src/conversions.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | use crate::{ 4 | debug::gl_err, 5 | error::{GlError, GlFallible}, 6 | gl_object::{NamedObject, ObjectName}, 7 | gl_types::{GLenum, GLsizei}, 8 | util::trimmed_type_name, 9 | }; 10 | 11 | use core::fmt::Debug; 12 | #[track_caller] 13 | pub(crate) fn check_sizei_inner(val: GLsizei) -> Result { 14 | u32::try_from(val).map_err(|_| { 15 | gl_err!("got negative GLsizei"); 16 | GlError::InvalidValue 17 | }) 18 | } 19 | macro_rules! sizei { 20 | ( 21 | $( 22 | $i:ident 23 | ),+ 24 | ) => { 25 | $( 26 | let $i = $crate::conversions::check_sizei_inner($i)?; 27 | )+ 28 | }; 29 | } 30 | pub(crate) use sizei; 31 | /// Trait defined for all custom bitfield and enum types which allows them to be unsafely created 32 | /// from an underlying `GLenum` (u32) value with checks on debug builds 33 | pub(crate) trait GlEnumGroup: Sized { 34 | fn from_enum(val: GLenum) -> Option; 35 | unsafe fn from_enum_noerr(val: GLenum) -> Self; 36 | } 37 | /// Helper trait to convert from "raw" [`GLenums`](crate::gl_types::GLenum) to wrappers around subsets of those that are valid for certain functions 38 | pub trait GLenumExt { 39 | fn try_into_enum(self) -> GlFallible; 40 | /// # Safety 41 | /// caller ensures value of this [`GLenum`] is valid for T 42 | unsafe fn into_enum(self) -> T; 43 | } 44 | 45 | impl> GLenumExt for U 46 | where 47 | T: GlEnumGroup, 48 | U: SrcType, 49 | { 50 | #[inline] 51 | unsafe fn into_enum(self) -> T { 52 | // Safety: Caller 53 | unsafe { T::from_enum_noerr(self.convert()) } 54 | } 55 | #[inline] 56 | fn try_into_enum(self) -> GlFallible { 57 | let val = self.convert(); 58 | T::from_enum(val).ok_or_else( 59 | #[inline] 60 | || { 61 | gl_err!(ty: Error, "invalid value {val} for GLenum group {}", trimmed_type_name::()); 62 | GlError::InvalidEnum.e() 63 | }, 64 | ) 65 | } 66 | } 67 | 68 | pub(crate) trait MaybeObjectName { 69 | fn get(self) -> Option>; 70 | } 71 | impl MaybeObjectName for ObjectName { 72 | fn get(self) -> Option> { 73 | Some(self) 74 | } 75 | } 76 | impl MaybeObjectName for u32 { 77 | fn get(self) -> Option> { 78 | Some(ObjectName::try_from_raw(self).expect("raw name was not a valid object name")) 79 | } 80 | } 81 | pub(crate) struct CurrentBinding; 82 | impl MaybeObjectName for CurrentBinding { 83 | fn get(self) -> Option> { 84 | None 85 | } 86 | } 87 | 88 | /// Trait that describes a type that may be returned from a glGet* function. 89 | pub(crate) trait GlDstType: Copy { 90 | fn from_uint(val: u32) -> Self; 91 | fn from_ulong(val: u64) -> Self; 92 | fn from_int(val: i32) -> Self; 93 | fn from_long(val: i64) -> Self; 94 | fn from_float(val: f32) -> Self; 95 | fn from_double(val: f64) -> Self; 96 | fn from_bool(val: bool) -> Self; 97 | } 98 | /// Trait that describes how to convert a given type into a value that may be returned according to the 99 | /// conversion rules for state items laid out by the GL spec 100 | pub(crate) trait SrcType: Copy { 101 | /// Convert from source type Self to the given destination type 102 | fn convert(self) -> Dst; 103 | } 104 | 105 | pub(crate) trait GlGetItem { 106 | unsafe fn write_out(&self, ptr: *mut Dst); 107 | } 108 | impl>> GlGetItem for T { 109 | #[inline] 110 | unsafe fn write_out(&self, ptr: *mut Dst) { 111 | // Safety: caller ensures `ptr` is valid for a write of type `Dst` 112 | unsafe { 113 | ptr::write(ptr, LocalInto::into(self.convert())); 114 | } 115 | } 116 | } 117 | 118 | impl>, const N: usize> GlGetItem for [T; N] { 119 | #[inline] 120 | unsafe fn write_out(&self, mut ptr: *mut Dst) { 121 | for i in self { 122 | // Safety: caller ensures `ptr` is valid for writes 123 | unsafe { ptr::write(ptr, LocalInto::into(i.convert())) }; 124 | // Safety: caller ensures `ptr` points to an allocation with enough space for a [Dst; N] 125 | unsafe { ptr = ptr.add(1) }; 126 | } 127 | } 128 | } 129 | 130 | //TODO get rid of this, it seemed like a good idea at the time but is now Not™ 131 | // Either an index or a lazily evaluated panic 132 | pub(crate) trait MaybeIndex: Copy + Sized + Debug { 133 | fn get(self) -> usize; 134 | fn get_opt(self) -> Option { 135 | Some(self.get()) 136 | } 137 | } 138 | impl MaybeIndex for u32 { 139 | fn get(self) -> usize { 140 | self as usize 141 | } 142 | } 143 | 144 | impl MaybeIndex for usize { 145 | fn get(self) -> usize { 146 | self 147 | } 148 | } 149 | #[derive(Clone, Copy)] 150 | pub(crate) struct NoIndex; 151 | impl MaybeIndex for NoIndex { 152 | #[inline] 153 | fn get(self) -> usize { 154 | panic!("Tried to index an array but no NoIndex was specified!") 155 | } 156 | #[inline] 157 | fn get_opt(self) -> Option { 158 | None 159 | } 160 | } 161 | impl Debug for NoIndex { 162 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 163 | f.write_str("[unindexed]") 164 | } 165 | } 166 | 167 | pub(crate) trait GlGetItemSliceExt { 168 | type SliceItem; 169 | unsafe fn write_out_index_mapped< 170 | I: MaybeIndex, 171 | Out: GlGetItem, 172 | F: FnOnce(&Self::SliceItem) -> Out, 173 | >( 174 | &self, 175 | idx: I, 176 | ptr: *mut Dst, 177 | map: F, 178 | ); 179 | unsafe fn write_out_index(&self, idx: I, ptr: *mut Dst) 180 | where 181 | Self::SliceItem: GlGetItem; 182 | } 183 | impl GlGetItemSliceExt for [T] { 184 | type SliceItem = T; 185 | 186 | unsafe fn write_out_index_mapped< 187 | I: MaybeIndex, 188 | Out: GlGetItem, 189 | F: FnOnce(&Self::SliceItem) -> Out, 190 | >( 191 | &self, 192 | idx: I, 193 | ptr: *mut Dst, 194 | map: F, 195 | ) { 196 | // Safety: Caller 197 | unsafe { map(&self[idx.get()]).write_out(ptr) } 198 | } 199 | 200 | unsafe fn write_out_index(&self, idx: I, ptr: *mut Dst) 201 | where 202 | Self::SliceItem: GlGetItem, 203 | { 204 | // Safety: Caller 205 | unsafe { self[idx.get()].write_out(ptr) }; 206 | } 207 | } 208 | 209 | #[derive(Copy, Clone)] 210 | #[repr(transparent)] 211 | struct LocalWrap(T); 212 | impl LocalFrom> for T { 213 | #[inline] 214 | fn from(value: LocalWrap) -> Self { 215 | value.0 216 | } 217 | } 218 | trait LocalFrom { 219 | fn from(value: T) -> Self; 220 | } 221 | trait LocalInto { 222 | fn into(self) -> T; 223 | } 224 | impl> LocalInto for U { 225 | #[inline] 226 | fn into(self) -> T { 227 | T::from(self) 228 | } 229 | } 230 | impl GlDstType for LocalWrap { 231 | #[inline] 232 | fn from_uint(val: u32) -> Self { 233 | Self(T::from_uint(val)) 234 | } 235 | #[inline] 236 | fn from_ulong(val: u64) -> Self { 237 | Self(T::from_ulong(val)) 238 | } 239 | #[inline] 240 | fn from_int(val: i32) -> Self { 241 | Self(T::from_int(val)) 242 | } 243 | #[inline] 244 | fn from_long(val: i64) -> Self { 245 | Self(T::from_long(val)) 246 | } 247 | #[inline] 248 | fn from_float(val: f32) -> Self { 249 | Self(T::from_float(val)) 250 | } 251 | #[inline] 252 | fn from_double(val: f64) -> Self { 253 | Self(T::from_double(val)) 254 | } 255 | #[inline] 256 | fn from_bool(val: bool) -> Self { 257 | Self(T::from_bool(val)) 258 | } 259 | } 260 | 261 | //TODO: wrap all of this in a macro 262 | impl SrcType for u32 { 263 | #[inline] 264 | fn convert(self) -> Dst { 265 | Dst::from_uint(self) 266 | } 267 | } 268 | impl SrcType for i32 { 269 | #[inline] 270 | fn convert(self) -> Dst { 271 | Dst::from_int(self) 272 | } 273 | } 274 | impl SrcType for u64 { 275 | #[inline] 276 | fn convert(self) -> Dst { 277 | Dst::from_ulong(self) 278 | } 279 | } 280 | impl SrcType for i64 { 281 | #[inline] 282 | fn convert(self) -> Dst { 283 | Dst::from_long(self) 284 | } 285 | } 286 | impl SrcType for usize { 287 | #[inline] 288 | fn convert(self) -> Dst { 289 | Dst::from_ulong(self as u64) 290 | } 291 | } 292 | impl SrcType for isize { 293 | #[inline] 294 | fn convert(self) -> Dst { 295 | Dst::from_long(self as i64) 296 | } 297 | } 298 | impl SrcType for f32 { 299 | #[inline] 300 | fn convert(self) -> Dst { 301 | Dst::from_float(self) 302 | } 303 | } 304 | impl SrcType for f64 { 305 | #[inline] 306 | fn convert(self) -> Dst { 307 | Dst::from_double(self) 308 | } 309 | } 310 | impl SrcType for bool { 311 | #[inline] 312 | fn convert(self) -> Dst { 313 | Dst::from_bool(self) 314 | } 315 | } 316 | impl GlDstType for u32 { 317 | #[inline] 318 | fn from_int(val: i32) -> Self { 319 | val.unsigned_abs() 320 | } 321 | #[inline] 322 | fn from_long(val: i64) -> Self { 323 | val.unsigned_abs() as Self 324 | } 325 | #[inline] 326 | fn from_uint(val: u32) -> Self { 327 | val 328 | } 329 | #[inline] 330 | fn from_ulong(val: u64) -> Self { 331 | val as Self 332 | } 333 | 334 | #[inline] 335 | fn from_float(val: f32) -> Self { 336 | val.round() as Self 337 | } 338 | 339 | #[inline] 340 | fn from_double(val: f64) -> Self { 341 | val.round() as Self 342 | } 343 | 344 | #[inline] 345 | fn from_bool(val: bool) -> Self { 346 | val as Self 347 | } 348 | } 349 | 350 | impl GlDstType for u64 { 351 | #[inline] 352 | fn from_int(val: i32) -> Self { 353 | val.unsigned_abs() as Self 354 | } 355 | #[inline] 356 | fn from_long(val: i64) -> Self { 357 | val.unsigned_abs() 358 | } 359 | #[inline] 360 | fn from_uint(val: u32) -> Self { 361 | val as Self 362 | } 363 | #[inline] 364 | fn from_ulong(val: u64) -> Self { 365 | val 366 | } 367 | 368 | #[inline] 369 | fn from_float(val: f32) -> Self { 370 | val.round() as Self 371 | } 372 | 373 | #[inline] 374 | fn from_double(val: f64) -> Self { 375 | val.round() as Self 376 | } 377 | 378 | #[inline] 379 | fn from_bool(val: bool) -> Self { 380 | val as Self 381 | } 382 | } 383 | impl GlDstType for i32 { 384 | #[inline] 385 | fn from_int(val: i32) -> Self { 386 | val 387 | } 388 | #[inline] 389 | fn from_long(val: i64) -> Self { 390 | val as Self 391 | } 392 | #[inline] 393 | fn from_uint(val: u32) -> Self { 394 | val as Self 395 | } 396 | #[inline] 397 | fn from_ulong(val: u64) -> Self { 398 | val as Self 399 | } 400 | 401 | #[inline] 402 | fn from_float(val: f32) -> Self { 403 | val.round() as Self 404 | } 405 | 406 | #[inline] 407 | fn from_double(val: f64) -> Self { 408 | val.round() as Self 409 | } 410 | 411 | #[inline] 412 | fn from_bool(val: bool) -> Self { 413 | val as Self 414 | } 415 | } 416 | 417 | impl GlDstType for i64 { 418 | #[inline] 419 | fn from_int(val: i32) -> Self { 420 | val as Self 421 | } 422 | #[inline] 423 | fn from_long(val: i64) -> Self { 424 | val 425 | } 426 | #[inline] 427 | fn from_uint(val: u32) -> Self { 428 | val as Self 429 | } 430 | #[inline] 431 | fn from_ulong(val: u64) -> Self { 432 | val as Self 433 | } 434 | #[inline] 435 | fn from_float(val: f32) -> Self { 436 | val.round() as Self 437 | } 438 | #[inline] 439 | fn from_double(val: f64) -> Self { 440 | val.round() as Self 441 | } 442 | #[inline] 443 | fn from_bool(val: bool) -> Self { 444 | val as Self 445 | } 446 | } 447 | impl GlDstType for f32 { 448 | #[inline] 449 | fn from_int(val: i32) -> Self { 450 | val as Self 451 | } 452 | #[inline] 453 | fn from_long(val: i64) -> Self { 454 | val as Self 455 | } 456 | #[inline] 457 | fn from_uint(val: u32) -> Self { 458 | val as Self 459 | } 460 | #[inline] 461 | fn from_ulong(val: u64) -> Self { 462 | val as Self 463 | } 464 | #[inline] 465 | fn from_float(val: f32) -> Self { 466 | val 467 | } 468 | #[inline] 469 | fn from_double(val: f64) -> Self { 470 | val as Self 471 | } 472 | #[inline] 473 | fn from_bool(val: bool) -> Self { 474 | val as u8 as Self 475 | } 476 | } 477 | impl GlDstType for f64 { 478 | #[inline] 479 | fn from_int(val: i32) -> Self { 480 | val as Self 481 | } 482 | #[inline] 483 | fn from_long(val: i64) -> Self { 484 | val as Self 485 | } 486 | #[inline] 487 | fn from_uint(val: u32) -> Self { 488 | val as Self 489 | } 490 | #[inline] 491 | fn from_ulong(val: u64) -> Self { 492 | val as Self 493 | } 494 | #[inline] 495 | fn from_float(val: f32) -> Self { 496 | val as Self 497 | } 498 | #[inline] 499 | fn from_double(val: f64) -> Self { 500 | val 501 | } 502 | #[inline] 503 | fn from_bool(val: bool) -> Self { 504 | val as u8 as Self 505 | } 506 | } 507 | impl GlDstType for bool { 508 | #[inline] 509 | fn from_uint(val: u32) -> Self { 510 | val != 0 511 | } 512 | 513 | #[inline] 514 | fn from_ulong(val: u64) -> Self { 515 | val != 0 516 | } 517 | 518 | #[inline] 519 | fn from_int(val: i32) -> Self { 520 | val != 0 521 | } 522 | 523 | #[inline] 524 | fn from_long(val: i64) -> Self { 525 | val != 0 526 | } 527 | 528 | #[inline] 529 | fn from_float(val: f32) -> Self { 530 | val != 0.0 531 | } 532 | 533 | #[inline] 534 | fn from_double(val: f64) -> Self { 535 | val != 0.0 536 | } 537 | 538 | #[inline] 539 | fn from_bool(val: bool) -> Self { 540 | val 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /oxidegl/src/context/state.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, ops::Deref, ptr}; 2 | 3 | use ahash::HashSet; 4 | use objc2_metal::{MTLBlendFactor, MTLBlendOperation}; 5 | 6 | use crate::{ 7 | gl_enums::{ 8 | BlendEquationModeEXT, BlendingFactor, ClearBufferMask, DepthFunction, ErrorCode, 9 | GL_CONTEXT_CORE_PROFILE_BIT, GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT, 10 | GL_CONTEXT_FLAG_NO_ERROR_BIT, StencilFunction, StencilOp, TriangleFace, 11 | }, 12 | gl_types::GLenum, 13 | pixel::PackUnpackState, 14 | texture::{Texture, TextureBindings}, 15 | util::bitflag_bits, 16 | }; 17 | 18 | use crate::{ 19 | commands::buffer::Buffer, 20 | debug::DebugState, 21 | framebuffer::{DrawBuffers, Framebuffer, MAX_COLOR_ATTACHMENTS}, 22 | gl_object::{NamedObjectList, ObjectName}, 23 | program::Program, 24 | shader::Shader, 25 | vao::Vao, 26 | }; 27 | 28 | #[derive(Debug, Default)] 29 | pub(crate) struct GlState { 30 | // Static/immutable properties, mostly for glGet. Should probably be turned into constants 31 | pub(crate) characteristics: Characteristics, 32 | /// Tracks whether a given GL capability is active or not 33 | pub(crate) caps: Capabilities, 34 | 35 | /// Current state of buffer bindings to this context 36 | pub(crate) buffer_bindings: BufferBindings, 37 | /// List of buffer object states 38 | pub(crate) buffer_list: NamedObjectList, 39 | 40 | /// List of VAO states 41 | pub(crate) vao_list: NamedObjectList, 42 | /// The current VAO to render with 43 | pub(crate) vao_binding: Option>, 44 | 45 | /// List of shader object states 46 | pub(crate) shader_list: NamedObjectList, 47 | /// Shaders that are queued for deletion when their reference count reaches 0 48 | pub(crate) shader_deletion_queue: HashSet>, 49 | 50 | /// List of shader program states 51 | pub(crate) program_list: NamedObjectList, 52 | /// Programs that are queued for deletion when their reference count reaches 0 53 | pub(crate) program_deletion_queue: HashSet>, 54 | /// Current program to render with 55 | pub(crate) program_binding: Option>, 56 | 57 | /// List of framebuffer object states 58 | pub(crate) framebuffer_list: NamedObjectList, 59 | /// The current framebuffer to render to (None: default FB) 60 | pub(crate) framebuffer_binding: Option>, 61 | /// draw buffer/attachment tracking for the default framebuffer 62 | pub(crate) default_draw_buffers: DrawBuffers, 63 | 64 | /// Current state of texture bindings to this context 65 | pub(crate) texture_bindings: TextureBindings, 66 | /// List of texture states 67 | pub(crate) texture_list: NamedObjectList, 68 | 69 | //TODO: these should be arrays in order to support viewport arrays 70 | pub(crate) scissor_box: PixelAlignedRect, 71 | pub(crate) viewport: PixelAlignedRect, 72 | 73 | pub(crate) clear_values: ClearState, 74 | pub(crate) stencil: StencilState, 75 | pub(crate) blend: BlendState, 76 | pub(crate) pack: PackUnpackState, 77 | pub(crate) writemasks: Writemasks, 78 | pub(crate) cull_face_mode: TriangleFace, 79 | 80 | pub(crate) depth_func: DepthFunction, 81 | 82 | /// storage for the debug state associated with this context (if it is not the current context). If this context is 83 | /// current, you'll need to use [`with_debug_state`](super::debug::with_debug_state) or 84 | /// [`with_debug_state_mut`](super::debug::with_debug_state_mut) to interact with the current debug state 85 | pub(crate) debug_state_holder: DebugStateHolder, 86 | /// Current error code 87 | pub(crate) error: ErrorCode, 88 | } 89 | 90 | #[derive(Debug)] 91 | pub(crate) struct DebugStateHolder(pub Option); 92 | impl Default for DebugStateHolder { 93 | #[inline] 94 | fn default() -> Self { 95 | Self(Some(DebugState::default())) 96 | } 97 | } 98 | 99 | impl Default for DepthFunction { 100 | #[inline] 101 | fn default() -> Self { 102 | DepthFunction::Less 103 | } 104 | } 105 | impl Default for TriangleFace { 106 | #[inline] 107 | fn default() -> Self { 108 | TriangleFace::Back 109 | } 110 | } 111 | 112 | #[derive(Clone, Copy, Debug)] 113 | pub(crate) struct ClearState { 114 | pub(crate) color: [f32; 4], 115 | pub(crate) depth: f32, 116 | pub(crate) stencil: u32, 117 | pub(crate) mask: ClearBufferMask, 118 | } 119 | impl Default for ClearState { 120 | #[inline] 121 | fn default() -> Self { 122 | Self { 123 | color: [0.0; 4], 124 | depth: 1.0, 125 | stencil: 0, 126 | mask: ClearBufferMask::empty(), 127 | } 128 | } 129 | } 130 | #[derive(Clone, Copy, Debug, Default)] 131 | pub(crate) struct StencilState { 132 | pub(crate) front: StencilFaceState, 133 | pub(crate) back: StencilFaceState, 134 | } 135 | 136 | #[derive(Clone, Copy, Debug)] 137 | pub struct StencilFaceState { 138 | /// Comparison function used to decide whether to discard the fragment or not 139 | pub(crate) func: StencilFunction, 140 | /// Bitmask whose least-significant N bits (where N is the bit-depth of the stencil component of the stencil buffer's pixel format) 141 | /// are bitwise AND-ed together with both the stencil value and reference before comparison 142 | pub(crate) mask: u32, 143 | /// reference value for comparison 144 | pub(crate) reference: u32, 145 | pub(crate) fail_action: StencilOp, 146 | pub(crate) depth_fail_action: StencilOp, 147 | pub(crate) depth_pass_action: StencilOp, 148 | } 149 | impl Default for StencilFaceState { 150 | #[inline] 151 | fn default() -> Self { 152 | Self { 153 | func: StencilFunction::Always, 154 | mask: u32::MAX, 155 | reference: 0, 156 | fail_action: StencilOp::Keep, 157 | depth_fail_action: StencilOp::Keep, 158 | depth_pass_action: StencilOp::Keep, 159 | } 160 | } 161 | } 162 | #[derive(Debug, Clone, Copy)] 163 | pub struct DrawbufferBlendState { 164 | pub(crate) blend_enabled: bool, 165 | pub(crate) src_rgb: BlendingFactor, 166 | pub(crate) src_alpha: BlendingFactor, 167 | pub(crate) dst_rgb: BlendingFactor, 168 | pub(crate) dst_alpha: BlendingFactor, 169 | pub(crate) eq_rgb: BlendEquationModeEXT, 170 | pub(crate) eq_alpha: BlendEquationModeEXT, 171 | } 172 | impl Default for DrawbufferBlendState { 173 | #[inline] 174 | fn default() -> Self { 175 | Self { 176 | blend_enabled: false, 177 | src_rgb: BlendingFactor::One, 178 | src_alpha: BlendingFactor::One, 179 | dst_rgb: BlendingFactor::Zero, 180 | dst_alpha: BlendingFactor::Zero, 181 | eq_rgb: BlendEquationModeEXT::FuncAdd, 182 | eq_alpha: BlendEquationModeEXT::FuncAdd, 183 | } 184 | } 185 | } 186 | #[derive(Debug, Default, Clone, Copy)] 187 | pub struct BlendState { 188 | pub(crate) blend_color: [f32; 4], 189 | pub(crate) drawbuffer_states: [DrawbufferBlendState; MAX_COLOR_ATTACHMENTS as usize], 190 | } 191 | 192 | impl From for MTLBlendFactor { 193 | #[inline] 194 | fn from(value: BlendingFactor) -> Self { 195 | #[allow(clippy::enum_glob_use)] 196 | use BlendingFactor::*; 197 | 198 | match value { 199 | // constants 200 | Zero => Self::Zero, 201 | One => Self::One, 202 | 203 | // source color/alpha 204 | SrcColor => Self::SourceColor, 205 | OneMinusSrcColor => Self::OneMinusSourceColor, 206 | SrcAlpha => Self::SourceAlpha, 207 | OneMinusSrcAlpha => Self::OneMinusSourceAlpha, 208 | SrcAlphaSaturate => Self::SourceAlphaSaturated, 209 | 210 | // destination color/alpha 211 | DstColor => Self::DestinationColor, 212 | OneMinusDstColor => Self::OneMinusDestinationColor, 213 | DstAlpha => Self::DestinationAlpha, 214 | OneMinusDstAlpha => Self::OneMinusDestinationAlpha, 215 | 216 | // constant color/alpha 217 | ConstantColor => Self::BlendColor, 218 | OneMinusConstantColor => Self::OneMinusBlendColor, 219 | ConstantAlpha => Self::BlendAlpha, 220 | OneMinusConstantAlpha => Self::OneMinusBlendAlpha, 221 | 222 | // source #2 color/alpha (for dual-source blending) 223 | Src1Alpha => Self::Source1Alpha, 224 | Src1Color => Self::Source1Color, 225 | OneMinusSrc1Color => Self::OneMinusSource1Color, 226 | OneMinusSrc1Alpha => Self::OneMinusSource1Alpha, 227 | } 228 | } 229 | } 230 | impl From for MTLBlendOperation { 231 | #[inline] 232 | fn from(value: BlendEquationModeEXT) -> Self { 233 | match value { 234 | BlendEquationModeEXT::FuncAdd => Self::Add, 235 | BlendEquationModeEXT::FuncReverseSubtract => Self::ReverseSubtract, 236 | BlendEquationModeEXT::FuncSubtract => Self::Subtract, 237 | BlendEquationModeEXT::Min => Self::Min, 238 | BlendEquationModeEXT::Max => Self::Max, 239 | } 240 | } 241 | } 242 | 243 | bitflag_bits! { 244 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 245 | #[repr(transparent)] 246 | pub struct Capabilities: u64 bits: { 247 | 248 | // don't include indexed caps 249 | // BLEND: 0, 250 | // SCISSOR_TEST: 19, 251 | 252 | MULTISAMPLE: 1, 253 | SAMPLE_ALPHA_TO_COVERAGE: 2, 254 | SAMPLE_ALPHA_TO_ONE: 3, 255 | SAMPLE_COVERAGE: 4, 256 | RASTERIZER_DISCARD: 5, 257 | FRAMEBUFFER_SRGB: 6, 258 | DEPTH_CLAMP: 7, 259 | TEXTURE_CUBE_MAP_SEAMLESS: 8, 260 | SAMPLE_MASK: 9, 261 | SAMPLE_SHADING: 10, 262 | DEBUG_OUTPUT_SYNCHRONOUS: 11, 263 | DEBUG_OUTPUT: 12, 264 | LINE_SMOOTH: 13, 265 | POLYGON_SMOOTH: 14, 266 | CULL_FACE: 15, 267 | DEPTH_TEST: 16, 268 | STENCIL_TEST: 17, 269 | DITHER: 18, 270 | POINT_SMOOTH: 20, 271 | LINE_STIPPLE: 21, 272 | POLYGON_STIPPLE: 22, 273 | LIGHTING: 23, 274 | COLOR_MATERIAL: 24, 275 | FOG: 25, 276 | NORMALIZE: 26, 277 | ALPHA_TEST: 27, 278 | TEXTURE_GEN_S: 28, 279 | TEXTURE_GEN_T: 29, 280 | TEXTURE_GEN_R: 30, 281 | TEXTURE_GEN_Q: 31, 282 | AUTO_NORMAL: 32, 283 | COLOR_LOGIC_OP: 33, 284 | POLYGON_OFFSET_POINT: 34, 285 | POLYGON_OFFSET_LINE: 35, 286 | POLYGON_OFFSET_FILL: 36, 287 | INDEX_LOGIC_OP: 37, 288 | NORMAL_ARRAY: 38, 289 | COLOR_ARRAY: 39, 290 | INDEX_ARRAY: 40, 291 | TEXTURE_COORD_ARRAY: 41, 292 | EDGE_FLAG_ARRAY: 42, 293 | PROGRAM_POINT_SIZE: 43, 294 | PRIMITIVE_RESTART: 44, 295 | PRIMITIVE_RESTART_FIXED_INDEX: 45, 296 | } 297 | 298 | } 299 | impl Capabilities { 300 | /// Returns true if any set bit in `cap` is also set in `self` 301 | #[inline] 302 | pub(crate) fn is_any_enabled(self, cap: Self) -> bool { 303 | self.intersects(cap) 304 | } 305 | #[inline] 306 | pub(crate) fn disable(&mut self, cap: Self) { 307 | *self = self.difference(cap); 308 | } 309 | #[inline] 310 | pub(crate) fn enable(&mut self, cap: Self) { 311 | *self = self.union(cap); 312 | } 313 | #[inline] 314 | pub(crate) fn set_to(&mut self, to: bool, cap: Self) -> bool { 315 | let mut r = self.intersects(cap); 316 | if to { 317 | r = !r; 318 | self.enable(cap); 319 | } else { 320 | self.disable(cap); 321 | } 322 | r 323 | } 324 | } 325 | impl Default for Capabilities { 326 | #[inline] 327 | fn default() -> Self { 328 | Self::empty() 329 | } 330 | } 331 | #[derive(Debug, Clone, Copy, Default, PartialEq)] 332 | #[repr(C)] 333 | pub(crate) struct PixelAlignedRect { 334 | // INVARIANT do not change layout without changing Deref impl! 335 | pub(crate) x: u32, 336 | pub(crate) y: u32, 337 | pub(crate) width: u32, 338 | pub(crate) height: u32, 339 | } 340 | impl Deref for PixelAlignedRect { 341 | type Target = [u32; 4]; 342 | #[inline] 343 | fn deref(&self) -> &Self::Target { 344 | // Safety: repr(C) layout guarantees of PixelAlignedRef give it an identical repr to [u32; 4] 345 | // reference creation is infallible because the pointer is originally derived from a reference and cannot be null 346 | unsafe { 347 | ptr::from_ref(self) 348 | .cast::<[u32; 4]>() 349 | .as_ref() 350 | .unwrap_unchecked() 351 | } 352 | } 353 | } 354 | 355 | pub const MAX_ATOMIC_COUNTER_BUFFER_BINDINGS: usize = 16; 356 | pub const MAX_SHADER_STORAGE_BUFFER_BINDINGS: usize = 16; 357 | pub const MAX_TRANSFORM_FEEDBACK_BUFFER_BINDINGS: usize = 16; 358 | pub const MAX_UNIFORM_BUFFER_BINDINGS: usize = 16; 359 | 360 | /// Keeps track of all buffer bindings to this OpenGL context 361 | #[derive(Debug, Clone, Copy, Default)] 362 | pub struct BufferBindings { 363 | /// Vertex attribute buffer 364 | pub(crate) array: Option>, 365 | /// Atomic counter storage 366 | pub(crate) atomic_counter: [Option>; MAX_ATOMIC_COUNTER_BUFFER_BINDINGS], 367 | /// Buffer copy source 368 | pub(crate) copy_read: Option>, 369 | /// Buffer copy destination 370 | pub(crate) copy_write: Option>, 371 | /// Indirect compute dispatch commands 372 | pub(crate) dispatch_indirect: Option>, 373 | /// Indirect draw command arguments 374 | pub(crate) draw_indirect: Option>, 375 | /// Vertex array indices 376 | pub(crate) element_array: Option>, 377 | /// Draw parameters 378 | pub(crate) parameter: Option>, 379 | /// Pixel read target 380 | pub(crate) pixel_pack: Option>, 381 | /// Texture data source 382 | pub(crate) pixel_unpack: Option>, 383 | /// Query results 384 | pub(crate) query: Option>, 385 | /// Shader storage buffers 386 | pub(crate) shader_storage: [Option>; MAX_SHADER_STORAGE_BUFFER_BINDINGS], 387 | /// Texture data buffer 388 | pub(crate) texture: Option>, 389 | /// Transform feedback result buffers 390 | pub(crate) transform_feedback: 391 | [Option>; MAX_TRANSFORM_FEEDBACK_BUFFER_BINDINGS], 392 | /// Uniform storage buffers 393 | pub(crate) uniform: [Option>; MAX_UNIFORM_BUFFER_BINDINGS], 394 | } 395 | 396 | #[derive(Debug, Copy, Clone)] 397 | pub(crate) struct Writemasks { 398 | pub(crate) color: [ColorWriteMask; MAX_COLOR_ATTACHMENTS as usize], 399 | pub(crate) depth: bool, 400 | pub(crate) stencil_front: u32, 401 | pub(crate) stencil_back: u32, 402 | } 403 | impl Default for Writemasks { 404 | fn default() -> Self { 405 | Self { 406 | color: [[true; 4].into(); MAX_COLOR_ATTACHMENTS as usize], 407 | depth: true, 408 | // all bits set for bitmasks 409 | stencil_front: u32::MAX, 410 | stencil_back: u32::MAX, 411 | } 412 | } 413 | } 414 | #[repr(C)] 415 | #[derive(Debug, Copy, Clone)] 416 | pub(crate) struct ColorWriteMask { 417 | pub(crate) red: bool, 418 | pub(crate) green: bool, 419 | pub(crate) blue: bool, 420 | pub(crate) alpha: bool, 421 | } 422 | // both of these impls should be inlined to no-ops, no need to transmute 423 | impl From for [bool; 4] { 424 | #[inline] 425 | fn from(value: ColorWriteMask) -> Self { 426 | [value.red, value.green, value.blue, value.alpha] 427 | } 428 | } 429 | impl From<[bool; 4]> for ColorWriteMask { 430 | #[inline] 431 | fn from(value: [bool; 4]) -> Self { 432 | Self { 433 | red: value[0], 434 | green: value[1], 435 | blue: value[2], 436 | alpha: value[3], 437 | } 438 | } 439 | } 440 | 441 | #[derive(Debug, Clone, Copy)] 442 | pub struct Characteristics { 443 | pub(crate) point_size: f32, 444 | pub(crate) point_size_range: [f32; 2], 445 | pub(crate) point_size_granularity: f32, 446 | pub(crate) line_width: f32, 447 | pub(crate) context_flags: GLenum, 448 | pub(crate) context_profile_mask: GLenum, 449 | pub(crate) num_extensions: u32, 450 | } 451 | impl Default for Characteristics { 452 | fn default() -> Self { 453 | Self { 454 | point_size_range: [1.0, 1.0], 455 | point_size_granularity: 0.0001, 456 | context_flags: GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT | GL_CONTEXT_FLAG_NO_ERROR_BIT, 457 | context_profile_mask: GL_CONTEXT_CORE_PROFILE_BIT, 458 | num_extensions: 1, 459 | point_size: 1.0, 460 | line_width: 1.0, 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /oxidegl/src/program.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, ptr::NonNull}; 2 | 3 | use ahash::{HashSet, HashSetExt}; 4 | use glslang::Compiler as GlslLangCompiler; 5 | use objc2::{rc::Retained, AllocAnyThread}; 6 | //use naga::back::msl::{Options, PipelineOptions}; 7 | use crate::{ 8 | 9 | debug::{gl_debug, gl_trace, with_debug_state}, 10 | shader::ShaderInternal, 11 | gl_enums::ShaderType, util::{NoDebug, ProtoObjRef}, 12 | }; 13 | use objc2_foundation::NSString; 14 | use objc2_metal::{MTLDevice, MTLFunction, MTLLibrary}; 15 | use spirv_cross2::{ 16 | compile::{msl::CompilerOptions, CompiledArtifact}, 17 | reflect::ResourceIter, 18 | targets::Msl, 19 | Compiler, Module, SpirvCrossError, 20 | }; 21 | 22 | use super::{ 23 | gl_object::{NamedObject, NamedObjectList, NoLateInit, ObjectName}, 24 | shader::Shader, 25 | }; 26 | #[derive(Debug)] 27 | pub enum ProgramStageBinding { 28 | Unbound, 29 | //Spirv shader stage is only allowed to have 30 | 31 | // what did he mean by this?? ^^ 32 | Spirv(ObjectName), 33 | Glsl(HashSet>), 34 | } 35 | impl ProgramStageBinding { 36 | #[expect( 37 | clippy::cast_possible_truncation, 38 | reason = "more than u32::MAX shaders cannot exist at once" 39 | )] 40 | pub(crate) fn shader_count(&self) -> u32 { 41 | match self { 42 | ProgramStageBinding::Unbound => 0, 43 | ProgramStageBinding::Spirv(_) => 1, 44 | ProgramStageBinding::Glsl(set) => set.len() as u32, 45 | } 46 | } 47 | #[inline] 48 | pub(crate) fn new_glsl(obj: ObjectName) -> Self { 49 | let mut set = HashSet::with_capacity(1); 50 | set.insert(obj); 51 | Self::Glsl(set) 52 | } 53 | #[inline] 54 | pub(crate) fn new_spirv(obj: ObjectName) -> Self { 55 | Self::Spirv(obj) 56 | } 57 | #[inline] 58 | pub(crate) fn add_shader(&mut self, shader: &Shader) { 59 | match (self, &shader.internal) { 60 | (binding , internal) if matches!(binding, Self::Unbound) => match internal { 61 | super::shader::ShaderInternal::Glsl(_) => { 62 | *binding = ProgramStageBinding::new_glsl(shader.name); 63 | } 64 | super::shader::ShaderInternal::Spirv(_) => { 65 | *binding = ProgramStageBinding::new_spirv(shader.name); 66 | } 67 | }, 68 | (Self::Glsl(hash_set), super::shader::ShaderInternal::Glsl(_)) => { 69 | hash_set.insert(shader.name); 70 | } 71 | (_, _) => panic!("tried to mix GLSL and SPIR-V shaders in a single stage or tried to link multiple SPIR-V shader objects to the same stage"), 72 | } 73 | } 74 | /// remove a shader from this program stage binding point. Returns whether the shader was present in the binding before removal 75 | #[inline] 76 | fn remove_shader(&mut self, name: ObjectName) -> bool { 77 | let b; 78 | (*self, b) = match self { 79 | ProgramStageBinding::Unbound => (Self::Unbound, false), 80 | Self::Spirv(bound_name) => (Self::Unbound, *bound_name == name), 81 | ProgramStageBinding::Glsl(hash_set) => { 82 | let has = hash_set.remove(&name); 83 | ( 84 | if hash_set.is_empty() { 85 | ProgramStageBinding::Unbound 86 | } else { 87 | ProgramStageBinding::Glsl(mem::take(hash_set)) 88 | }, 89 | has, 90 | ) 91 | } 92 | }; 93 | b 94 | } 95 | pub(crate) fn is_empty(&self) -> bool { 96 | matches!(self, Self::Unbound) 97 | } 98 | } 99 | #[derive(Debug)] 100 | pub struct Program { 101 | pub(crate) name: ObjectName, 102 | pub(crate) refcount: u32, 103 | pub(crate) vertex_shaders: ProgramStageBinding, 104 | pub(crate) fragment_shaders: ProgramStageBinding, 105 | pub(crate) compute_shaders: ProgramStageBinding, 106 | pub(crate) latest_linkage: Option, 107 | pub(crate) info_log: String, 108 | } 109 | impl Program { 110 | pub(crate) fn new_named(name: ObjectName) -> Self { 111 | gl_debug!("created program {:?}", name); 112 | Self { 113 | name, 114 | refcount: 0, 115 | vertex_shaders: ProgramStageBinding::Unbound, 116 | fragment_shaders: ProgramStageBinding::Unbound, 117 | compute_shaders: ProgramStageBinding::Unbound, 118 | latest_linkage: None, 119 | info_log: String::new(), 120 | } 121 | } 122 | pub(crate) fn retain_program(&mut self) { 123 | self.refcount += 1; 124 | } 125 | pub(crate) fn release_program(&mut self) -> bool { 126 | if self.refcount <= 1 { 127 | self.refcount = 0; 128 | return true; 129 | } 130 | self.refcount -= 1; 131 | false 132 | } 133 | #[inline] 134 | pub(crate) fn attached_shader_count(&self) -> u32 { 135 | self.vertex_shaders.shader_count() 136 | + self.fragment_shaders.shader_count() 137 | + self.compute_shaders.shader_count() 138 | } 139 | #[inline] 140 | pub(crate) fn debug_log_str(&mut self, msg: &str) { 141 | gl_debug!("{msg}"); 142 | self.info_log.push('\n'); 143 | self.info_log.push_str(msg); 144 | } 145 | #[inline] 146 | pub(crate) fn attach_shader(&mut self, shader: &mut Shader) { 147 | self.get_stage_binding(shader.stage).add_shader(shader); 148 | self.debug_log_str(&format!("attached {:?} to {:?}", shader.name, self.name)); 149 | shader.retain(); 150 | } 151 | #[inline] 152 | fn get_stage_binding(&mut self, stage: ShaderType) -> &mut ProgramStageBinding { 153 | match stage { 154 | crate::gl_enums::ShaderType::FragmentShader => &mut self.fragment_shaders, 155 | crate::gl_enums::ShaderType::VertexShader => &mut self.vertex_shaders, 156 | crate::gl_enums::ShaderType::ComputeShader => &mut self.compute_shaders, 157 | _ => unreachable!(), 158 | } 159 | } 160 | /// Detaches shader. Returns whether the shader can be safely removed from the shader list if it is queued for deletion 161 | #[inline] 162 | pub(crate) fn detach_shader(&mut self, shader: &mut Shader) -> bool { 163 | self.debug_log_str(&format!("detached {:?} from {:?}", shader.name, self.name)); 164 | if self 165 | .get_stage_binding(shader.stage) 166 | .remove_shader(shader.name) 167 | { 168 | shader.release_shader() 169 | } else { 170 | false 171 | } 172 | } 173 | #[inline] 174 | fn link_stage( 175 | shader_list: &NamedObjectList, 176 | device: &ProtoObjRef, 177 | binding: &mut ProgramStageBinding, 178 | glslang_compiler: &GlslLangCompiler, 179 | label: Option<&Retained>, 180 | ) -> Result> { 181 | macro_rules! err_ret { 182 | ($e:expr) => { 183 | return Err($e.to_string().into_boxed_str()) 184 | }; 185 | } 186 | let mut used_shaders = Vec::with_capacity(1); 187 | let mut stage = None; 188 | let mut stage_spirv = match binding { 189 | ProgramStageBinding::Unbound => unreachable!(), 190 | ProgramStageBinding::Spirv(_) => todo!(), 191 | ProgramStageBinding::Glsl(hash_set) => { 192 | let mut program = glslang_compiler.create_program(); 193 | // dont need to recheck shader name validity here, we can just panic on failiure 194 | for shader in hash_set.iter().copied().map(|name| shader_list.get(name)) { 195 | used_shaders.push(shader.name.to_raw().to_string().into_boxed_str()); 196 | let ShaderInternal::Glsl(internal) = &shader.internal else { 197 | unreachable!() 198 | }; 199 | stage = Some(shader.stage); 200 | let Some(glslang_shader) = &internal.latest_shader else { 201 | err_ret!("Tried to link a program with a shader that did not compile!"); 202 | }; 203 | program.add_shader(glslang_shader); 204 | } 205 | 206 | let spirv_src = program 207 | .compile( 208 | stage 209 | .expect("stage should have been set") 210 | .to_glslang_stage(), 211 | ) 212 | .map_err(|e| e.to_string())?; 213 | let module = Module::from_words(&spirv_src); 214 | Compiler::::new(module).map_err(|e| e.to_string())? 215 | } 216 | }; 217 | stage_spirv 218 | .add_discrete_descriptor_set(3) 219 | .map_err(|e| e.to_string().into_boxed_str())?; 220 | 221 | let entry_name = format!("{:?}_{}_main", stage.unwrap(), used_shaders.join("_")); 222 | 223 | let model = stage_spirv 224 | .execution_model() 225 | .expect("failed to get execution model"); 226 | let previous_entry_cleansed = stage_spirv 227 | .cleansed_entry_point_name("main", model) 228 | .expect("failed to cleanse entry point name") 229 | .expect("cleansed entry point was null"); 230 | stage_spirv 231 | .rename_entry_point(previous_entry_cleansed, entry_name.clone(), model) 232 | .expect("failed to rename spirv entry point"); 233 | 234 | let mut opts = CompilerOptions::default(); 235 | opts.version = (2, 1).into(); 236 | opts.argument_buffers = true; 237 | let artifact = stage_spirv.compile(&opts).map_err(|e| e.to_string())?; 238 | 239 | let msl_src = format!("{artifact}"); 240 | gl_trace!(src: ShaderCompiler, "transformed metal sources for stage:\n{msl_src}"); 241 | 242 | let lib = device 243 | .newLibraryWithSource_options_error(&NSString::from_str(&msl_src), None) 244 | .map_err(|e| e.to_string())?; 245 | if let Some(label) = label { 246 | lib.setLabel(Some(label)); 247 | } 248 | // TODO: coalesce ungrouped (named) uniforms into a single uniform block with a hashmap for by-identifier uniform lookup 249 | Ok(LinkedStage { 250 | function: lib 251 | .newFunctionWithName(&NSString::from_str(&entry_name)) 252 | .unwrap(), 253 | lib, 254 | resources: LinkedProgramResources::get_from_compiler(&artifact) 255 | .expect("failed to get resource bindings during program linkage!"), 256 | artifact: artifact.into(), 257 | }) 258 | } 259 | //TODO async shader compilation 260 | #[inline] 261 | pub(crate) fn link( 262 | &mut self, 263 | shader_list: &mut NamedObjectList, 264 | device: &ProtoObjRef, 265 | ) { 266 | //TODO errors 267 | self.latest_linkage = None; 268 | gl_debug!(src: ShaderCompiler, "attempting to link {:?}", self.name); 269 | let glslang_compiler = 270 | GlslLangCompiler::acquire().expect("failed to acquire glslang instance"); 271 | let mut new_linkage = LinkedProgram { 272 | fragment: None, 273 | vertex: None, 274 | compute: None, 275 | }; 276 | let label = with_debug_state(|state| state.get_label(self.name)) 277 | .flatten() 278 | .as_deref() 279 | .map(|c| 280 | // Safety: 281 | unsafe { 282 | let alloc = NSString::alloc(); 283 | NSString::initWithCString_encoding( 284 | alloc, 285 | NonNull::new_unchecked(c.as_ptr().cast_mut()), 286 | NSString::defaultCStringEncoding(), 287 | ) 288 | .expect("failed to create NSString") 289 | }); 290 | if !self.vertex_shaders.is_empty() { 291 | gl_trace!(src: ShaderCompiler, "linking vertex shaders"); 292 | match Self::link_stage( 293 | shader_list, 294 | device, 295 | &mut self.vertex_shaders, 296 | glslang_compiler, 297 | label.as_ref(), 298 | ) { 299 | Ok(v) => new_linkage.vertex = Some(v), 300 | Err(s) => { 301 | self.debug_log_str(&s); 302 | return; 303 | } 304 | } 305 | } 306 | if !self.fragment_shaders.is_empty() { 307 | gl_trace!(src: ShaderCompiler, "linking fragment shaders"); 308 | match Self::link_stage( 309 | shader_list, 310 | device, 311 | &mut self.fragment_shaders, 312 | glslang_compiler, 313 | label.as_ref(), 314 | ) { 315 | Ok(v) => new_linkage.fragment = Some(v), 316 | Err(s) => { 317 | self.debug_log_str(&s); 318 | return; 319 | } 320 | } 321 | } 322 | if !self.compute_shaders.is_empty() { 323 | gl_trace!(src: ShaderCompiler, "linking compute shaders"); 324 | match Self::link_stage( 325 | shader_list, 326 | device, 327 | &mut self.compute_shaders, 328 | glslang_compiler, 329 | label.as_ref(), 330 | ) { 331 | Ok(v) => new_linkage.vertex = Some(v), 332 | Err(s) => { 333 | self.debug_log_str(&s); 334 | return; 335 | } 336 | } 337 | } 338 | self.latest_linkage = Some(new_linkage); 339 | } 340 | } 341 | 342 | impl NamedObject for Program { 343 | type LateInitType = NoLateInit; 344 | } 345 | 346 | #[derive(Debug)] 347 | pub struct LinkedProgram { 348 | pub(crate) fragment: Option, 349 | pub(crate) vertex: Option, 350 | pub(crate) compute: Option, 351 | } 352 | #[inline] 353 | fn to_resource_vec( 354 | iter: ResourceIter<'_>, 355 | compiler: &Compiler, 356 | ) -> Result, SpirvCrossError> { 357 | let mut vec = Vec::with_capacity(iter.len()); 358 | for v in iter { 359 | vec.push(ProgramResource { 360 | name: v.name.to_string().into_boxed_str(), 361 | binding: compiler 362 | .decoration(v.id, spirv_cross2::spirv::Decoration::Binding)? 363 | .map(|v| v.as_literal().expect("failed to convert literal")), 364 | location: compiler 365 | .decoration(v.id, spirv_cross2::spirv::Decoration::Location)? 366 | .map(|v| v.as_literal().expect("failed to convert literal")), 367 | }); 368 | } 369 | Ok(vec) 370 | } 371 | #[derive(Debug)] 372 | pub struct LinkedProgramResources { 373 | pub(crate) uniform_buffers: Vec, 374 | pub(crate) shader_storage_buffers: Vec, 375 | pub(crate) atomic_counter_buffers: Vec, 376 | pub(crate) stage_inputs: Vec, 377 | pub(crate) plain_uniforms: Vec, 378 | } 379 | impl LinkedProgramResources { 380 | //TODO XFBs 381 | fn get_from_compiler(spirvc: &Compiler) -> Result { 382 | let value = spirvc.shader_resources()?; 383 | let uniform_buffers = to_resource_vec( 384 | value.resources_for_type(spirv_cross2::reflect::ResourceType::UniformBuffer)?, 385 | spirvc, 386 | )?; 387 | let shader_storage_buffers = to_resource_vec( 388 | value.resources_for_type(spirv_cross2::reflect::ResourceType::StorageBuffer)?, 389 | spirvc, 390 | )?; 391 | let atomic_counter_buffers = to_resource_vec( 392 | value.resources_for_type(spirv_cross2::reflect::ResourceType::AtomicCounter)?, 393 | spirvc, 394 | )?; 395 | let stage_inputs = to_resource_vec( 396 | value.resources_for_type(spirv_cross2::reflect::ResourceType::StageInput)?, 397 | spirvc, 398 | )?; 399 | let plain_uniforms = to_resource_vec( 400 | value.resources_for_type(spirv_cross2::reflect::ResourceType::GlPlainUniform)?, 401 | spirvc, 402 | )?; 403 | Ok(Self { 404 | uniform_buffers, 405 | shader_storage_buffers, 406 | atomic_counter_buffers, 407 | stage_inputs, 408 | plain_uniforms, 409 | }) 410 | } 411 | } 412 | #[derive(Debug)] 413 | pub struct ProgramResource { 414 | pub(crate) name: Box, 415 | pub(crate) binding: Option, 416 | pub(crate) location: Option, 417 | } 418 | #[derive(Debug)] 419 | pub struct LinkedStage { 420 | /// the entry point for this stage 421 | pub(crate) function: ProtoObjRef, 422 | /// a retained reference to the metal library that contains the entry point function for this stage 423 | pub(crate) lib: ProtoObjRef, 424 | /// the `spirv_cross` artifact/module that was compiled to the metal lib given above 425 | pub(crate) artifact: NoDebug>, 426 | /// Resources 427 | pub(crate) resources: LinkedProgramResources, 428 | } 429 | --------------------------------------------------------------------------------