├── libs └── .gitkeep ├── examples ├── build │ ├── .gitkeep │ └── web │ │ ├── .gitkeep │ │ └── index.html ├── capture │ ├── red.png │ └── README.md ├── learn_wgpu │ ├── README.md │ └── beginner │ │ ├── tutorial1_window_sdl │ │ ├── README.md │ │ └── tutorial1_window.odin │ │ ├── tutorial2_surface_glfw │ │ └── README.md │ │ ├── tutorial2_surface_sdl │ │ └── README.md │ │ ├── tutorial1_window_glfw │ │ ├── README.md │ │ └── tutorial1_window.odin │ │ ├── tutorial2_surface_challenge │ │ ├── README.md │ │ └── tutorial2_surface_challenge.odin │ │ ├── tutorial3_pipeline │ │ ├── shader.wgsl │ │ └── tutorial3_pipeline.odin │ │ ├── tutorial4_buffer │ │ └── shader.wgsl │ │ ├── tutorial3_pipeline_challenge │ │ └── challenge.wgsl │ │ ├── tutorial5_textures │ │ └── shader.wgsl │ │ ├── tutorial6_uniforms │ │ └── shader.wgsl │ │ └── tutorial7_instancing │ │ └── shader.wgsl ├── microui │ ├── microui.png │ ├── README.md │ └── microui_example.odin ├── triangle │ ├── triangle.png │ ├── surface_js.odin │ ├── README.md │ ├── surface_windows.odin │ ├── surface_darwin.odin │ ├── surface_linux.odin │ ├── triangle.wgsl │ ├── triangle_js.odin │ └── triangle_native.odin ├── cube_textured │ ├── cube.png │ ├── README.md │ ├── cube_textured.wgsl │ └── vertex.odin ├── image_blur │ ├── image_blur.png │ ├── README.md │ ├── fullscreen_textured_quad.wgsl │ └── blur.wgsl ├── triangle_msaa │ ├── triangle_msaa.png │ ├── README.md │ └── triangle_msaa.odin ├── texture_arrays │ ├── texture_arrays.png │ ├── README.md │ ├── non_uniform_indexing.wgsl │ └── indexing.wgsl ├── textured_cube │ ├── README.md │ ├── textured_cube.wgsl │ └── cube.odin ├── rotating_cube │ ├── README.md │ ├── rotating_cube.wgsl │ └── cube.odin ├── cubemap │ ├── README.md │ ├── cube.odin │ └── cubemap.wgsl ├── common │ ├── types.odin │ ├── utils.odin │ ├── model.odin │ └── resources.odin ├── info │ ├── README.md │ └── info.odin ├── stencil_triangles │ ├── stencil_triangles.wgsl │ └── README.md ├── compute │ ├── README.md │ └── compute.wgsl ├── cube │ ├── cube.wgsl │ └── vertex.odin ├── imgui │ └── README.md ├── coordinate_system │ └── coordinate_system.wgsl ├── cameras │ ├── cube.wgsl │ ├── cameera.odin │ ├── wasd_camera.odin │ └── arcball_camera.odin ├── square │ └── square.wgsl ├── instanced_cube │ └── instanced.wgsl ├── fractal_cube │ ├── fractal_cube.wgsl │ └── cube.odin ├── clear_screen │ └── clear_screen.odin ├── build.bat └── build.sh ├── utils ├── application │ ├── mouse_js.odin │ ├── mathf.odin │ ├── timer_windows.odin │ ├── math.odin │ ├── constants.odin │ ├── video_mode_js.odin │ ├── app_native.odin │ ├── mouse_native.odin │ ├── video_mode.odin │ ├── keyboard.odin │ ├── video_mode_native.odin │ ├── log.odin │ ├── run_js.odin │ ├── key.odin │ ├── events.odin │ ├── window.odin │ ├── run_native.odin │ ├── mouse.odin │ ├── microui.odin │ └── timer.odin ├── imgui │ ├── README.md │ └── imgui_impl_wgpu.wgsl ├── microui │ ├── helpers.odin │ └── microui.wgsl ├── shaders │ ├── srgb_to_linear.wgsl │ ├── linear_to_srgb.wgsl │ └── shaders.odin ├── glfw │ ├── common.odin │ ├── surface_windows.odin │ ├── surface_darwin.odin │ └── surface_linux.odin ├── sdl3 │ ├── common.odin │ ├── surface_darwin.odin │ ├── surface_windows.odin │ └── surface_linux.odin └── sdl2 │ ├── surface_windows.odin │ ├── common.odin │ ├── surface_darwin.odin │ └── surface_linux.odin ├── .gitattributes ├── odinfmt.json ├── .gitmodules ├── ols.json.example ├── .editorconfig ├── .gitignore ├── version.odin ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── log.odin ├── pipeline_layout.odin ├── texture_view.odin ├── range.odin ├── query_set.odin ├── render_bundle.odin ├── sampler.odin ├── bind_group.odin ├── render_bundle_encoder_native.odin ├── command_buffer.odin ├── device_native.odin ├── compute_pass_native.odin ├── bind_group_layout.odin ├── render_pipeline.odin ├── callbacks.odin ├── compute_pipeline.odin ├── color_blend.odin ├── compute_pass.odin ├── shader_module.odin ├── to_bytes.odin ├── README.md ├── utils.odin ├── CHANGELOG.md └── .sublime-project /libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/build/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/build/web/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/capture/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/capture/red.png -------------------------------------------------------------------------------- /examples/learn_wgpu/README.md: -------------------------------------------------------------------------------- 1 | # Learn Wgpu 2 | 3 | Reference: 4 | -------------------------------------------------------------------------------- /examples/microui/microui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/microui/microui.png -------------------------------------------------------------------------------- /examples/triangle/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/triangle/triangle.png -------------------------------------------------------------------------------- /examples/cube_textured/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/cube_textured/cube.png -------------------------------------------------------------------------------- /examples/image_blur/image_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/image_blur/image_blur.png -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial1_window_sdl/README.md: -------------------------------------------------------------------------------- 1 | # The Window 2 | 3 | Open a `SDL2` window with no context. 4 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial2_surface_glfw/README.md: -------------------------------------------------------------------------------- 1 | # Surface 2 | 3 | Clear the surface window with a color. 4 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial2_surface_sdl/README.md: -------------------------------------------------------------------------------- 1 | # Surface 2 | 3 | Clear the surface window with a color. 4 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial1_window_glfw/README.md: -------------------------------------------------------------------------------- 1 | # The Window 2 | 3 | Open a `GLFW` window with no context. 4 | -------------------------------------------------------------------------------- /examples/triangle_msaa/triangle_msaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/triangle_msaa/triangle_msaa.png -------------------------------------------------------------------------------- /examples/texture_arrays/texture_arrays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Capati/wgpu-odin/HEAD/examples/texture_arrays/texture_arrays.png -------------------------------------------------------------------------------- /utils/application/mouse_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | package application 3 | 4 | mouse_update :: proc "contextless" (app := app_context) #no_bounds_check { 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See 2 | # https://github.com/github/linguist/blob/249bbd1c2ffc631ca2ec628da26be5800eec3d48/docs/overrides.md#vendored-code 3 | 4 | resources/** linguist-vendored 5 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial2_surface_challenge/README.md: -------------------------------------------------------------------------------- 1 | # Surface Challenge 2 | 3 | **Note**: From this point the framework is used. 4 | 5 | Clear the surface window with a color from the mouse position. 6 | -------------------------------------------------------------------------------- /utils/application/mathf.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import la "core:math/linalg" 5 | 6 | Mat4 :: la.Matrix4f32 7 | Vec2 :: la.Vector2f32 8 | Vec3 :: la.Vector3f32 9 | Vec :: la.Vector4f32 10 | -------------------------------------------------------------------------------- /utils/imgui/README.md: -------------------------------------------------------------------------------- 1 | # Odin ImGui 2 | 3 | This is the `wgpu` backend implemented in Odin. 4 | 5 | Please, refer to the [Odin ImGui][] to learn more about the bindings: 6 | 7 | [Odin ImGui]: https://github.com/Capati/odin-imgui 8 | -------------------------------------------------------------------------------- /odinfmt.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json", 3 | "character_width": 100, 4 | "tabs": true, 5 | "spaces": 4, 6 | "tabs_width": 4, 7 | "newline_style": "LF" 8 | } 9 | -------------------------------------------------------------------------------- /utils/application/timer_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | package application 3 | 4 | // Core 5 | import win32 "core:sys/windows" 6 | 7 | _win32_timer_init :: proc "contextless" (t: ^Timer) { 8 | win32.timeBeginPeriod(1) 9 | } 10 | -------------------------------------------------------------------------------- /examples/texture_arrays/README.md: -------------------------------------------------------------------------------- 1 | # Texture Arrays 2 | 3 | ## Build 4 | 5 | ```shell 6 | odin build ./texture_arrays -out:./build/ 7 | ``` 8 | 9 | ## Screenshots 10 | 11 | ![Texture Arrays](./texture_arrays.png) 12 | -------------------------------------------------------------------------------- /examples/textured_cube/README.md: -------------------------------------------------------------------------------- 1 | # Rotating Cube Textured 2 | 3 | This example shows how to bind and sample textures. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./rotating_cube_textured -out:./build/ 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/cube_textured/README.md: -------------------------------------------------------------------------------- 1 | # Cube 2 | 3 | This example renders a textured cube. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./cube -out:./build/ 9 | ``` 10 | 11 | ## Screenshots 12 | 13 | ![Textured Cube](./cube.png) 14 | -------------------------------------------------------------------------------- /examples/rotating_cube/README.md: -------------------------------------------------------------------------------- 1 | # Rotating Cube 2 | 3 | This example shows how to upload uniform data every frame to render a rotating object. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./rotating_cube -out:./build/ 9 | ``` 10 | -------------------------------------------------------------------------------- /utils/microui/helpers.odin: -------------------------------------------------------------------------------- 1 | package wgpu_microui 2 | 3 | // Vendor 4 | import mu "vendor:microui" 5 | 6 | update_window_rect :: proc(ctx: ^mu.Context, window_name: string, rect: mu.Rect) { 7 | if c := mu.get_container(ctx, window_name); c != nil { 8 | c.rect = rect 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/triangle_msaa/README.md: -------------------------------------------------------------------------------- 1 | # Triangle 4x MSAA 2 | 3 | This uses the same triangle example but with 4x MSAA. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./triangle_msaa -out:./build/ 9 | ``` 10 | 11 | ## Screenshots 12 | 13 | ![Triangle](./triangle_msaa.png) 14 | -------------------------------------------------------------------------------- /utils/application/math.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import la "core:math/linalg" 4 | 5 | Vec2u :: [2]u32 6 | Vec3u :: [3]u32 7 | Vec4u :: [4]u32 8 | 9 | Vec2i :: [2]int 10 | Vec3i :: [3]int 11 | Vec4i :: [4]int 12 | 13 | Vec2f :: la.Vector2f32 14 | Vec3f :: la.Vector3f32 15 | Vec4f :: la.Vector4f32 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "examples/build/assets"] 2 | path = examples/build/assets 3 | url = https://github.com/Capati/wgpu-odin-assets.git 4 | [submodule "libs/imgui"] 5 | path = libs/imgui 6 | url = https://github.com/Capati/odin-imgui.git 7 | [submodule "libs/tobj"] 8 | path = libs/tobj 9 | url = https://github.com/Capati/odin-tobj.git 10 | -------------------------------------------------------------------------------- /examples/cubemap/README.md: -------------------------------------------------------------------------------- 1 | # Cubemap 2 | 3 | This example shows how to render and sample from a cubemap texture. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./cubemap -out:./build/ 9 | ``` 10 | 11 | Using the build script: 12 | 13 | ```shell 14 | .\build.bat cubemap 15 | ``` 16 | 17 | ```shell 18 | make cubemap 19 | ``` 20 | -------------------------------------------------------------------------------- /utils/shaders/srgb_to_linear.wgsl: -------------------------------------------------------------------------------- 1 | // Converts an sRGB color to linear (physical) space 2 | fn srgb_to_linear(srgb: vec3) -> vec3 { 3 | let cutoff = vec3(0.04045); 4 | let linear_low = srgb / 12.92; 5 | let linear_high = pow((srgb + 0.055) / 1.055, vec3(2.4)); 6 | return select(linear_high, linear_low, srgb <= cutoff); 7 | } 8 | -------------------------------------------------------------------------------- /utils/shaders/linear_to_srgb.wgsl: -------------------------------------------------------------------------------- 1 | // Converts a linear (physical) color to sRGB space 2 | fn linear_to_srgb(linear: vec3) -> vec3 { 3 | let cutoff = vec3(0.0031308); 4 | let srgb_low = linear * 12.92; 5 | let srgb_high = pow(linear, vec3(1.0 / 2.4)) * 1.055 - 0.055; 6 | return select(srgb_high, srgb_low, linear <= cutoff); 7 | } 8 | -------------------------------------------------------------------------------- /examples/triangle/surface_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | package triangle 3 | 4 | // Local packages 5 | import wgpu "../.." 6 | 7 | os_get_surface :: proc(instance: wgpu.Instance) -> (surface: wgpu.Surface) { 8 | return wgpu.InstanceCreateSurface(instance, { 9 | target = wgpu.SurfaceSourceCanvasHTMLSelector { 10 | selector = "#canvas", 11 | }, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /examples/triangle/README.md: -------------------------------------------------------------------------------- 1 | # Triangle 2 | 3 | This example renders a triangle to a window. 4 | 5 | ## Build 6 | 7 | ```shell 8 | odin build ./triangle -out:./build/ 9 | ``` 10 | 11 | Using the build script: 12 | 13 | ```shell 14 | .\build.bat triangle 15 | ``` 16 | 17 | ```shell 18 | make triangle 19 | ``` 20 | 21 | ## Screenshots 22 | 23 | ![Triangle](./triangle.png) 24 | -------------------------------------------------------------------------------- /examples/common/types.odin: -------------------------------------------------------------------------------- 1 | package examples_common 2 | 3 | // Core 4 | import la "core:math/linalg" 5 | 6 | // Note: Models centered on (0, 0, 0) will be halfway inside the clipping area. This is 7 | // for when you aren't using a camera matrix. 8 | OPEN_GL_TO_WGPU_MATRIX :: la.Matrix4f32 { 9 | 1.0, 0.0, 0.0, 0.0, 10 | 0.0, 1.0, 0.0, 0.0, 11 | 0.0, 0.0, 0.5, 0.5, 12 | 0.0, 0.0, 0.0, 1.0, 13 | } 14 | -------------------------------------------------------------------------------- /utils/glfw/common.odin: -------------------------------------------------------------------------------- 1 | package wgpu_glfw 2 | 3 | // Core 4 | import "vendor:glfw" 5 | 6 | // Local packages 7 | import wgpu "../../" 8 | 9 | create_surface :: proc "c" ( 10 | window: glfw.WindowHandle, 11 | instance: wgpu.Instance, 12 | ) -> ( 13 | surface: wgpu.Surface, 14 | ) { 15 | descriptor := get_surface_descriptor(window) 16 | return wgpu.InstanceCreateSurface(instance, descriptor) 17 | } 18 | -------------------------------------------------------------------------------- /utils/sdl3/common.odin: -------------------------------------------------------------------------------- 1 | package wgpu_sdl3 2 | 3 | // Vendor 4 | import sdl "vendor:sdl3" 5 | 6 | // Local packages 7 | import wgpu "../../" 8 | 9 | create_surface :: proc "c" ( 10 | window: ^sdl.Window, 11 | instance: wgpu.Instance, 12 | ) -> ( 13 | surface: wgpu.Surface, 14 | ) { 15 | descriptor := get_surface_descriptor(window) 16 | return wgpu.InstanceCreateSurface(instance, descriptor) 17 | } 18 | -------------------------------------------------------------------------------- /examples/info/README.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | Print current WGPU version and selected adapter information. 4 | 5 | ```shell 6 | odin build ./info -out:./build/ 7 | ``` 8 | 9 | Output example: 10 | 11 | ```shell 12 | WGPU version: 0.17.0.2 13 | 14 | Selected device: 15 | 16 | NVIDIA GeForce RTX 3060 17 | Driver: 536.99 18 | Type: Discrete GPU with separate CPU/GPU memory 19 | Backend: Vulkan API 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/image_blur/README.md: -------------------------------------------------------------------------------- 1 | # Image Blur Example 2 | 3 | This example demonstrates how to use the compute shader to blur a input texture. 4 | 5 | ## Build 6 | 7 | To build the example, use the following command: 8 | 9 | ```shell 10 | odin build ./image_blur -out:./build/ 11 | ``` 12 | 13 | Replace `` with your desired output name. 14 | 15 | ## Screenshots 16 | 17 | ![Image Blur Example](./image_blur.png) 18 | -------------------------------------------------------------------------------- /examples/stencil_triangles/stencil_triangles.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) position: vec4, 3 | }; 4 | 5 | @vertex 6 | fn vs_main(@location(0) position: vec4) -> VertexOutput { 7 | var result: VertexOutput; 8 | result.position = position; 9 | return result; 10 | } 11 | 12 | @fragment 13 | fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { 14 | return vec4(0.97, 0.88, 0.21, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /examples/compute/README.md: -------------------------------------------------------------------------------- 1 | # Hello Compute 2 | 3 | Runs a compute shader to determine the number of iterations of the rules from 4 | Collatz Conjecture 5 | 6 | - If n is even, n = n/2 7 | - If n is odd, n = 3n+1 8 | 9 | that it will take to finish and reach the number `1`. 10 | 11 | Output: 12 | 13 | ```shell 14 | Steps: [0, 1, 7, 2] 15 | ``` 16 | 17 | ## Build 18 | 19 | ```shell 20 | odin build ./compute -out:./build/ 21 | ``` 22 | -------------------------------------------------------------------------------- /ols.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", 3 | "enable_format": true, 4 | "enable_semantic_tokens": true, 5 | "enable_document_symbols": true, 6 | "enable_hover": true, 7 | "enable_snippets": true, 8 | "enable_checker_only_saved": true, 9 | "enable_inlay_hints": false, 10 | "enable_fake_methods": true, 11 | "checker_args": "-vet -debug -vet-cast -vet-style -vet-using-param -strict-style -warnings-as-errors" 12 | } 13 | -------------------------------------------------------------------------------- /utils/sdl3/surface_darwin.odin: -------------------------------------------------------------------------------- 1 | #+build darwin 2 | package wgpu_sdl3 3 | 4 | // Vendor 5 | import sdl "vendor:sdl3" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 11 | view := sdl.Metal_CreateView(window) 12 | layer := sdl.Metal_GetLayer(view) 13 | 14 | // Setup surface information 15 | descriptor.target = wgpu.SurfaceSourceMetalLayer { 16 | layer = layer, 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /examples/capture/README.md: -------------------------------------------------------------------------------- 1 | # Capture 2 | 3 | This example shows how to capture an image by rendering it to a texture, copying the texture to a buffer, and retrieving it from the buffer. 4 | 5 | This create ./red.png with all pixels red and size 100x200. 6 | 7 | ## Build 8 | 9 | ```shell 10 | odin build ./capture -out:./build/ 11 | ``` 12 | 13 | **Note**: If you get `write_png` reference errors, try to run the Makefile on `/vendor/stb/src`. 14 | 15 | ## Screenshots 16 | 17 | ![Capture](./red.png) 18 | -------------------------------------------------------------------------------- /utils/application/constants.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import la "core:math/linalg" 5 | 6 | // Local packages 7 | import wgpu "../../" 8 | 9 | DEFAULT_DEPTH_FORMAT :: wgpu.TextureFormat.Depth24Plus 10 | 11 | // Note: Models centered on (0, 0, 0) will be halfway inside the clipping area. This is 12 | // for when you aren't using a camera matrix. 13 | OPEN_GL_TO_WGPU_MATRIX :: la.Matrix4f32 { 14 | 1.0, 0.0, 0.0, 0.0, 15 | 0.0, 1.0, 0.0, 0.0, 16 | 0.0, 0.0, 0.5, 0.5, 17 | 0.0, 0.0, 0.0, 1.0, 18 | } 19 | -------------------------------------------------------------------------------- /examples/stencil_triangles/README.md: -------------------------------------------------------------------------------- 1 | # Stencil Triangles 2 | 3 | This example renders two different sized triangles to display three same sized triangles, by demonstrating the use of stencil buffers. 4 | 5 | First it draws a small "mask" triangle, which sets the stencil buffer at every pixel to 1. 6 | 7 | Then, it draws a larger "outer" triangle which only touches pixels where the stencil buffer is less than 1. 8 | 9 | ## Build 10 | 11 | ```shell 12 | odin build ./stencil_triangles -out:./build/ 13 | ``` 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | insert_final_newline = ignore 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.bat] 21 | indent_style = tab 22 | end_of_line = crlf 23 | 24 | [Makefile] 25 | indent_style = tab 26 | 27 | [*.wgsl] 28 | indent_style = tab 29 | -------------------------------------------------------------------------------- /utils/sdl2/surface_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | package wgpu_sdl2 3 | 4 | // Vendor 5 | import sdl "vendor:sdl2" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 11 | wmInfo := get_sys_info(window) 12 | 13 | // Setup surface information 14 | descriptor.target = wgpu.SurfaceSourceWindowsHWND { 15 | hinstance = wmInfo.info.win.hinstance, 16 | hwnd = wmInfo.info.win.window, 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /examples/common/utils.odin: -------------------------------------------------------------------------------- 1 | package examples_common 2 | 3 | // Core 4 | import "core:math" 5 | import la "core:math/linalg" 6 | 7 | create_view_projection_matrix :: proc "contextless" (aspect: f32) -> la.Matrix4f32 { 8 | // 72 deg FOV (2 * PI / 5 radians) 9 | projection := la.matrix4_perspective_f32(2 * math.PI / 5, aspect, 1.0, 100.0) 10 | view := la.matrix4_look_at_f32( 11 | eye = {1.1, 1.1, 1.1}, 12 | centre = {0.0, 0.0, 0.0}, 13 | up = {0.0, 1.0, 0.0}, 14 | ) 15 | return OPEN_GL_TO_WGPU_MATRIX * projection * view 16 | } 17 | -------------------------------------------------------------------------------- /examples/triangle/surface_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | package triangle 3 | 4 | // Core 5 | import win32 "core:sys/windows" 6 | 7 | // Vendor 8 | import "vendor:glfw" 9 | 10 | // Local packages 11 | import wgpu "../.." 12 | 13 | os_get_surface :: proc(instance: wgpu.Instance) -> (surface: wgpu.Surface) { 14 | return wgpu.InstanceCreateSurface(instance, { 15 | target = wgpu.SurfaceSourceWindowsHWND { 16 | hinstance = win32.GetModuleHandleW(nil), 17 | hwnd = glfw.GetWin32Window(state.os.window), 18 | }, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /examples/cube/cube.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOut { 2 | @builtin(position) position: vec4f, 3 | @location(0) color: vec4f 4 | } 5 | 6 | @group(0) @binding(0) 7 | var mvpMat : mat4x4; 8 | 9 | @vertex 10 | fn vs_main( 11 | @location(0) position: vec3f, 12 | @location(1) color: vec3f 13 | ) -> VertexOut { 14 | var output : VertexOut; 15 | output.position = mvpMat * vec4f(position, 1.0); 16 | output.color = vec4f(color, 1.0); 17 | return output; 18 | } 19 | 20 | @fragment 21 | fn fs_main(fragData: VertexOut) -> @location(0) vec4f { 22 | return fragData.color; 23 | } 24 | -------------------------------------------------------------------------------- /utils/glfw/surface_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | package wgpu_glfw 3 | 4 | // Core 5 | import win "core:sys/windows" 6 | 7 | // Vendor 8 | import "vendor:glfw" 9 | 10 | // Local packages 11 | import wgpu "../../" 12 | 13 | get_surface_descriptor :: proc "c" ( 14 | window: glfw.WindowHandle, 15 | ) -> ( 16 | descriptor: wgpu.SurfaceDescriptor, 17 | ) { 18 | instance := win.GetModuleHandleW(nil) 19 | 20 | // Setup surface information 21 | descriptor.target = wgpu.SurfaceSourceWindowsHWND { 22 | hinstance = instance, 23 | hwnd = glfw.GetWin32Window(window), 24 | } 25 | 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /utils/sdl2/common.odin: -------------------------------------------------------------------------------- 1 | package wgpu_sdl2 2 | 3 | // Vendor 4 | import sdl "vendor:sdl2" 5 | 6 | // Local packages 7 | import wgpu "../../" 8 | 9 | @(private) 10 | get_sys_info :: proc "c" (window: ^sdl.Window) -> (wmInfo: sdl.SysWMinfo) { 11 | sdl.GetVersion(&wmInfo.version) 12 | ensure_contextless(bool(sdl.GetWindowWMInfo(window, &wmInfo))) 13 | return 14 | } 15 | 16 | create_surface :: proc "c" ( 17 | window: ^sdl.Window, 18 | instance: wgpu.Instance, 19 | ) -> ( 20 | surface: wgpu.Surface, 21 | ) { 22 | descriptor := get_surface_descriptor(window) 23 | return wgpu.InstanceCreateSurface(instance, descriptor) 24 | } 25 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial1_window_glfw/tutorial1_window.odin: -------------------------------------------------------------------------------- 1 | package tutorial1_window_glfw 2 | 3 | // Core 4 | import "core:fmt" 5 | 6 | // Vendor 7 | import "vendor:glfw" 8 | 9 | main :: proc() { 10 | if !glfw.Init() { 11 | panic("[glfw] init failure") 12 | } 13 | 14 | glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) 15 | window := glfw.CreateWindow(960, 540, "Tutorial 1 - Window", nil, nil) 16 | 17 | for !glfw.WindowShouldClose(window) { 18 | glfw.PollEvents() 19 | 20 | // We will render here... 21 | } 22 | 23 | glfw.DestroyWindow(window) 24 | glfw.Terminate() 25 | 26 | fmt.println("Exiting...") 27 | } 28 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial3_pipeline/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct VertexOutput { 4 | @builtin(position) clip_position: vec4, 5 | }; 6 | 7 | @vertex 8 | fn vs_main( 9 | @builtin(vertex_index) in_vertex_index: u32, 10 | ) -> VertexOutput { 11 | var out: VertexOutput; 12 | let x = f32(1 - i32(in_vertex_index)) * 0.5; 13 | let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; 14 | out.clip_position = vec4(x, y, 0.0, 1.0); 15 | return out; 16 | } 17 | 18 | // Fragment shader 19 | 20 | @fragment 21 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 22 | return vec4(0.4, 0.3, 0.2, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /examples/imgui/README.md: -------------------------------------------------------------------------------- 1 | # ImGui Example 2 | 3 | This example demonstrates how to use Dear ImGui with WGPU backend in Odin to create graphical 4 | user interfaces. 5 | 6 | ## Compile ImGui 7 | 8 | First, ensure you cloned this repository with submodules, then follow the build instructions 9 | provided in the link below. You just need the `glfw` backend, while the `wgpu` backend can be 10 | skipped as it's implemented in Odin. 11 | 12 | 13 | 14 | ## Build 15 | 16 | ```shell 17 | build.bat imgui run 18 | ``` 19 | 20 | ```shell 21 | odin build ./imgui -out:./build/ 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial4_buffer/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct VertexInput { 4 | @location(0) position: vec3, 5 | @location(1) color: vec3, 6 | }; 7 | 8 | struct VertexOutput { 9 | @builtin(position) clip_position: vec4, 10 | @location(0) color: vec3, 11 | }; 12 | 13 | @vertex 14 | fn vs_main( 15 | model: VertexInput, 16 | ) -> VertexOutput { 17 | var out: VertexOutput; 18 | out.color = model.color; 19 | out.clip_position = vec4(model.position, 1.0); 20 | return out; 21 | } 22 | 23 | // Fragment shader 24 | 25 | @fragment 26 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 27 | return vec4(in.color, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /utils/application/video_mode_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | package application 3 | 4 | // Core 5 | import "core:sys/wasm/js" 6 | 7 | get_video_modes :: proc(allocator := context.allocator) -> (modes: []Video_Mode) { 8 | return 9 | } 10 | 11 | get_video_mode :: proc() -> (mode: Video_Mode) { 12 | // Get body dimensions as proxy for viewport 13 | body_rect := js.get_bounding_client_rect("body") 14 | 15 | // Get device pixel ratio 16 | dpi := js.device_pixel_ratio() 17 | 18 | // Viewport dimensions 19 | mode.width = u32(f64(body_rect.width) * dpi) 20 | mode.height = u32(f64(body_rect.height) * dpi) 21 | 22 | // Default refresh rate 23 | refresh_rate := 60 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /examples/coordinate_system/coordinate_system.wgsl: -------------------------------------------------------------------------------- 1 | struct Output { 2 | @builtin(position) position : vec4, 3 | @location(0) uv : vec2 4 | }; 5 | 6 | @vertex 7 | fn vs_main( 8 | @location(0) inPos: vec3, 9 | @location(1) inUV: vec2 10 | ) -> Output { 11 | var output: Output; 12 | output.uv = inUV; 13 | output.position = vec4(inPos.xyz, 1.0); 14 | return output; 15 | } 16 | 17 | @group(0) @binding(0) var textureColor: texture_2d; 18 | @group(0) @binding(1) var samplerColor: sampler; 19 | 20 | @fragment 21 | fn fs_main( 22 | @location(0) inUV : vec2 23 | ) -> @location(0) vec4 { 24 | return textureSample(textureColor, samplerColor, inUV); 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | *.lib 3 | *.dll 4 | *.pdb 5 | *.a 6 | *.dylib 7 | *.exe 8 | *.so 9 | *.obj 10 | *.o 11 | *.exp 12 | *.ilk 13 | *.res 14 | *.manifest 15 | 16 | /build/**/* 17 | /examples/build/* 18 | !/examples/build/assets 19 | !/examples/build/.gitkeep 20 | !/examples/build/web/.gitkeep 21 | !/examples/build/web/index.html 22 | 23 | # Keep obj models in this folder 24 | !/utils/tobj/**/*.obj 25 | 26 | # IDE/Editor 27 | /.vscode 28 | !/.vscode/launch.json 29 | !/.vscode/tasks.json 30 | *.sublime-workspace 31 | *.code-workspace 32 | .idea/ 33 | *.swp 34 | *.swo 35 | 36 | # Logs and temp 37 | /tmp 38 | *.log 39 | 40 | # OS 41 | **/.DS_Store 42 | Thumbs.db 43 | desktop.ini 44 | 45 | # Project specific 46 | ols.json 47 | -------------------------------------------------------------------------------- /examples/triangle/surface_darwin.odin: -------------------------------------------------------------------------------- 1 | #+build darwin 2 | package triangle 3 | 4 | // Core 5 | import NS "core:sys/darwin/Foundation" 6 | import CA "vendor:darwin/QuartzCore" 7 | 8 | // Vendor 9 | import "vendor:glfw" 10 | 11 | // Local packages 12 | import wgpu "../.." 13 | 14 | os_get_surface :: proc(instance: wgpu.Instance) -> (surface: wgpu.Surface) { 15 | native_window := (^NS.Window)(glfw.GetCocoaWindow(state.os.window)) 16 | 17 | metal_layer := CA.MetalLayer.layer() 18 | defer metal_layer->release() 19 | 20 | native_window->contentView()->setLayer(metal_layer) 21 | 22 | return wgpu.InstanceCreateSurface(instance, { 23 | target = wgpu.SurfaceSourceMetalLayer { 24 | layer = metal_layer, 25 | }, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /examples/cameras/cube.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | modelViewProjectionMatrix : mat4x4f, 3 | } 4 | 5 | @group(0) @binding(0) var uniforms : Uniforms; 6 | @group(0) @binding(1) var mySampler: sampler; 7 | @group(0) @binding(2) var myTexture: texture_2d; 8 | 9 | struct VertexOutput { 10 | @builtin(position) Position : vec4f, 11 | @location(0) fragUV : vec2f, 12 | } 13 | 14 | @vertex 15 | fn vs_main( 16 | @location(0) position : vec4f, 17 | @location(1) uv : vec2f 18 | ) -> VertexOutput { 19 | return VertexOutput(uniforms.modelViewProjectionMatrix * position, uv); 20 | } 21 | 22 | @fragment 23 | fn fs_main(@location(0) fragUV: vec2f) -> @location(0) vec4f { 24 | return textureSample(myTexture, mySampler, fragUV); 25 | } 26 | -------------------------------------------------------------------------------- /utils/glfw/surface_darwin.odin: -------------------------------------------------------------------------------- 1 | #+build darwin 2 | package wgpu_glfw 3 | 4 | // Core 5 | import NS "core:sys/darwin/Foundation" 6 | import CA "vendor:darwin/QuartzCore" 7 | import "vendor:glfw" 8 | 9 | // Local packages 10 | import wgpu "../../" 11 | 12 | get_surface_descriptor :: proc "c" ( 13 | window: glfw.WindowHandle, 14 | ) -> ( 15 | descriptor: wgpu.SurfaceDescriptor, 16 | ) { 17 | nativeWindow := (^NS.Window)(glfw.GetCocoaWindow(window)) 18 | 19 | metalLayer := CA.MetalLayer.layer() 20 | defer metalLayer->release() 21 | 22 | nativeWindow->contentView()->setLayer(metalLayer) 23 | 24 | // Setup surface information 25 | descriptor.target = wgpu.SurfaceSourceMetalLayer { 26 | layer = metalLayer, 27 | } 28 | 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /utils/application/app_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package application 3 | 4 | // Core 5 | import "base:runtime" 6 | 7 | // Vendor 8 | import "vendor:glfw" 9 | 10 | @(private, init) 11 | _init :: proc "contextless" () { 12 | when ODIN_DEBUG { 13 | _glfw_error_proc :: proc "c" (error: i32, description: cstring) { 14 | runtime.print_string("[GLFW] --- [") 15 | runtime.print_int(int(error)) 16 | runtime.print_string("]: ") 17 | runtime.print_string(string(description)) 18 | runtime.print_string("\n") 19 | } 20 | glfw.SetErrorCallback(_glfw_error_proc) 21 | } 22 | ensure_contextless(bool(glfw.Init()), "Failed to initialize GLFW") 23 | } 24 | 25 | @(private, fini) 26 | _fini :: proc "contextless" () { 27 | glfw.Terminate() 28 | } 29 | -------------------------------------------------------------------------------- /utils/sdl2/surface_darwin.odin: -------------------------------------------------------------------------------- 1 | #+build darwin 2 | package wgpu_sdl2 3 | 4 | // Vendor 5 | import NS "core:sys/darwin/Foundation" 6 | import CA "vendor:darwin/QuartzCore" 7 | import sdl "vendor:sdl2" 8 | 9 | // Local packages 10 | import wgpu "../../" 11 | 12 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 13 | wmInfo := get_sys_info(window) 14 | 15 | nativeWindow := (^NS.Window)(wmInfo.info.cocoa.window) 16 | 17 | metalLayer := CA.MetalLayer.layer() 18 | defer metalLayer->release() 19 | 20 | nativeWindow->contentView()->setLayer(metalLayer) 21 | 22 | // Setup surface information 23 | descriptor.target = wgpu.SurfaceSourceMetalLayer { 24 | layer = metalLayer, 25 | } 26 | 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial3_pipeline_challenge/challenge.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct VertexOutput { 4 | @builtin(position) clip_position: vec4, 5 | @location(0) color: vec2, 6 | }; 7 | 8 | @vertex 9 | fn vs_main( 10 | @builtin(vertex_index) in_vertex_index: u32, 11 | ) -> VertexOutput { 12 | var out: VertexOutput; 13 | let x = f32(1 - i32(in_vertex_index)) * 0.5; 14 | let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; 15 | out.color = vec2(smoothstep(-0.5, 0.5, x), smoothstep(-0.5, 0.5, y)); 16 | out.clip_position = vec4(x, y, 0.0, 1.0); 17 | return out; 18 | } 19 | 20 | // Fragment shader 21 | 22 | @fragment 23 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 24 | return vec4(in.color, 0.5, 1.0); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /utils/sdl2/surface_linux.odin: -------------------------------------------------------------------------------- 1 | #+build linux 2 | package wgpu_sdl2 3 | 4 | // Vendor 5 | import sdl "vendor:sdl2" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 11 | wmInfo := get_sys_info(window) 12 | 13 | // Setup surface information 14 | if wmInfo.subsystem == .WAYLAND { 15 | descriptor.target = wgpu.SurfaceSourceWaylandSurface { 16 | display = wmInfo.info.wl.display, 17 | surface = wmInfo.info.wl.surface, 18 | } 19 | } else if wmInfo.subsystem == .X11 { 20 | descriptor.target = wgpu.SurfaceSourceXlibWindow { 21 | display = wmInfo.info.x11.display, 22 | window = cast(u64)wmInfo.info.x11.window, 23 | } 24 | } 25 | 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /utils/glfw/surface_linux.odin: -------------------------------------------------------------------------------- 1 | #+build linux 2 | package wgpu_glfw 3 | 4 | // Core 5 | import "vendor:glfw" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" ( 11 | window: glfw.WindowHandle, 12 | ) -> ( 13 | descriptor: wgpu.SurfaceDescriptor, 14 | ) { 15 | switch glfw.GetPlatform() { 16 | case glfw.PLATFORM_WAYLAND: 17 | descriptor.target = wgpu.SurfaceSourceWaylandSurface { 18 | display = glfw.GetWaylandDisplay(), 19 | surface = glfw.GetWaylandWindow(window), 20 | } 21 | case glfw.PLATFORM_X11: 22 | descriptor.target = wgpu.SurfaceSourceXlibWindow { 23 | display = glfw.GetX11Display(), 24 | window = u64(glfw.GetX11Window(window)), 25 | } 26 | case: 27 | panic_contextless("Unsupported platform") 28 | } 29 | 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /examples/square/square.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location(0) position : vec3, 3 | @location(1) color : vec4 4 | }; 5 | 6 | struct VertexOutput { 7 | @builtin(position) Position : vec4, 8 | @location(0) fragColor : vec4 9 | }; 10 | 11 | @vertex 12 | fn vs_main(input : VertexInput) -> VertexOutput { 13 | var output : VertexOutput; 14 | output.fragColor = input.color; 15 | output.Position = vec4(input.position, 1.0); 16 | return output; 17 | } 18 | 19 | struct FragmentInput { 20 | @location(0) fragColor : vec4 21 | }; 22 | 23 | struct FragmentOutput { 24 | @location(0) outColor : vec4 25 | }; 26 | 27 | @fragment 28 | fn fs_main(input : FragmentInput) -> FragmentOutput { 29 | var output : FragmentOutput; 30 | output.outColor = input.fragColor; 31 | return output; 32 | } 33 | -------------------------------------------------------------------------------- /examples/rotating_cube/rotating_cube.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | modelViewProjectionMatrix: mat4x4f, 3 | } 4 | 5 | @binding(0) @group(0) var uniforms: Uniforms; 6 | 7 | struct VertexOutput { 8 | @builtin(position) Position: vec4f, 9 | @location(0) fragUV: vec2f, 10 | @location(1) fragColor: vec4f, 11 | } 12 | 13 | @vertex 14 | fn vs_main( 15 | @location(0) position: vec4f, 16 | @location(1) color: vec4f, 17 | @location(2) uv: vec2f 18 | ) -> VertexOutput { 19 | var output: VertexOutput; 20 | output.Position = uniforms.modelViewProjectionMatrix * position; 21 | output.fragUV = uv; 22 | output.fragColor = color; 23 | return output; 24 | } 25 | 26 | @fragment 27 | fn fs_main( 28 | @location(0) fragUV: vec2f, 29 | @location(1) fragColor: vec4f 30 | ) -> @location(0) vec4f { 31 | return fragColor; 32 | } 33 | -------------------------------------------------------------------------------- /utils/sdl3/surface_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | package wgpu_sdl3 3 | 4 | // Vendor 5 | import sdl "vendor:sdl3" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 11 | hinstance := sdl.GetPointerProperty( 12 | sdl.GetWindowProperties(window), 13 | sdl.PROP_WINDOW_WIN32_INSTANCE_POINTER, 14 | nil, 15 | ) 16 | hwnd := sdl.GetPointerProperty( 17 | sdl.GetWindowProperties(window), 18 | sdl.PROP_WINDOW_WIN32_HWND_POINTER, 19 | nil, 20 | ) 21 | 22 | ensure_contextless(hwnd != nil) 23 | ensure_contextless(hinstance != nil) 24 | 25 | // Setup surface information 26 | descriptor.target = wgpu.SurfaceSourceWindowsHWND { 27 | hinstance = hinstance, 28 | hwnd = hwnd, 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial5_textures/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct VertexInput { 4 | @location(0) position: vec3, 5 | @location(1) tex_coords: vec2, 6 | } 7 | 8 | struct VertexOutput { 9 | @builtin(position) clip_position: vec4, 10 | @location(0) tex_coords: vec2, 11 | } 12 | 13 | @vertex 14 | fn vs_main( 15 | model: VertexInput, 16 | ) -> VertexOutput { 17 | var out: VertexOutput; 18 | out.tex_coords = model.tex_coords; 19 | out.clip_position = vec4(model.position, 1.0); 20 | return out; 21 | } 22 | 23 | // Fragment shader 24 | 25 | @group(0) @binding(0) 26 | var t_diffuse: texture_2d; 27 | @group(0)@binding(1) 28 | var s_diffuse: sampler; 29 | 30 | @fragment 31 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 32 | return textureSample(t_diffuse, s_diffuse, in.tex_coords); 33 | } 34 | -------------------------------------------------------------------------------- /examples/triangle/surface_linux.odin: -------------------------------------------------------------------------------- 1 | #+build linux 2 | package triangle 3 | 4 | // Core 5 | import "vendor:glfw" 6 | 7 | // Local packages 8 | import wgpu "../.." 9 | 10 | os_get_surface :: proc(instance: wgpu.Instance) -> (surface: wgpu.Surface) { 11 | descriptor: wgpu.SurfaceDescriptor 12 | 13 | switch glfw.GetPlatform() { 14 | case glfw.PLATFORM_WAYLAND: 15 | descriptor.target = wgpu.SurfaceSourceWaylandSurface { 16 | display = glfw.GetWaylandDisplay(), 17 | surface = glfw.GetWaylandWindow(state.os.window), 18 | } 19 | case glfw.PLATFORM_X11: 20 | descriptor.target = wgpu.SurfaceSourceXlibWindow { 21 | display = glfw.GetX11Display(), 22 | window = u64(glfw.GetX11Window(state.os.window)), 23 | } 24 | case: 25 | panic("Unsupported Linux platform") 26 | } 27 | 28 | return wgpu.InstanceCreateSurface(instance, descriptor) 29 | } 30 | -------------------------------------------------------------------------------- /version.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package webgpu 3 | 4 | // Vendor 5 | import "vendor:wgpu" 6 | 7 | Version :: struct { 8 | major: u8, 9 | minor: u8, 10 | patch: u8, 11 | build: u8, 12 | } 13 | 14 | BINDINGS_VERSION :: Version { 15 | wgpu.BINDINGS_VERSION.x, 16 | wgpu.BINDINGS_VERSION.y, 17 | wgpu.BINDINGS_VERSION.z, 18 | wgpu.BINDINGS_VERSION.w, 19 | } 20 | BINDINGS_VERSION_STRING :: wgpu.BINDINGS_VERSION_STRING 21 | 22 | // Return a struct with `major`, `minor`, `patch` and `build` version of wgpu. 23 | GetVersion :: proc() -> (version: Version) { 24 | rawVersion := wgpu.GetVersion() 25 | 26 | version.major = u8((rawVersion >> 24) & 0xFF) 27 | version.minor = u8((rawVersion >> 16) & 0xFF) 28 | version.patch = u8((rawVersion >> 8) & 0xFF) 29 | version.build = u8(rawVersion & 0xFF) 30 | 31 | return 32 | } 33 | 34 | GetVersionNumber :: wgpu.GetVersion 35 | -------------------------------------------------------------------------------- /utils/microui/microui.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location(0) position: vec2, 3 | @location(1) uv: vec2, 4 | @location(2) color: vec4, 5 | }; 6 | 7 | struct VertexOutput { 8 | @builtin(position) clip_position: vec4, 9 | @location(0) uv: vec2, 10 | @location(1) color: vec4, 11 | }; 12 | 13 | @group(0) @binding(0) var atlas_texture: texture_2d; 14 | @group(0) @binding(1) var atlas_sampler: sampler; 15 | 16 | @vertex 17 | fn vs_main(in: VertexInput) -> VertexOutput { 18 | var out: VertexOutput; 19 | out.clip_position = vec4(in.position, 0.0, 1.0); 20 | out.uv = in.uv; 21 | out.color = in.color; 22 | return out; 23 | } 24 | 25 | @fragment 26 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 27 | let atlas_color = textureSample(atlas_texture, atlas_sampler, in.uv); 28 | return atlas_color * in.color; 29 | } 30 | -------------------------------------------------------------------------------- /examples/cubemap/cube.odin: -------------------------------------------------------------------------------- 1 | package cube_map 2 | 3 | Vertex :: struct { 4 | position: [4]f32, 5 | tex_coords: [2]f32, 6 | } 7 | 8 | CUBE_VERTEX_DATA := []Vertex { 9 | // Front face 10 | vertex(-1, -1, 1, 0, 0), // 0 11 | vertex( 1, -1, 1, 1, 0), // 1 12 | vertex( 1, 1, 1, 1, 1), // 2 13 | vertex(-1, 1, 1, 0, 1), // 3 14 | // Back face 15 | vertex(-1, -1, -1, 1, 0), // 4 16 | vertex( 1, -1, -1, 0, 0), // 5 17 | vertex( 1, 1, -1, 0, 1), // 6 18 | vertex(-1, 1, -1, 1, 1), // 7 19 | } 20 | 21 | vertex :: proc "contextless" (pos1, pos2, pos3, tc1, tc2: f32) -> Vertex { 22 | return Vertex{position = {pos1, pos2, pos3, 1}, tex_coords = {tc1, tc2}} 23 | } 24 | 25 | CUBE_INDICES_DATA :: []u16 { 26 | 0, 1, 2, 2, 3, 0, // front 27 | 5, 4, 7, 7, 6, 5, // back 28 | 1, 5, 6, 6, 2, 1, // right 29 | 4, 0, 3, 3, 7, 4, // left 30 | 3, 2, 6, 6, 7, 3, // top 31 | 4, 5, 1, 1, 0, 4, // bottom 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "(gdb) Launch", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/examples/build", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceFolder}", 12 | "environment": [], 13 | "externalConsole": false, 14 | "MIMode": "gdb", 15 | "setupCommands": [ 16 | { 17 | "text": "-enable-pretty-printing", 18 | "ignoreFailures": true 19 | }, 20 | { 21 | "text": "-gdb-set disassembly-flavor intel", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | }, 26 | { 27 | "name": "(Windows) Launch", 28 | "type": "cppvsdbg", 29 | "request": "launch", 30 | "program": "${workspaceFolder}/examples/build/timestamp_query.exe", 31 | "args": [], 32 | "stopAtEntry": false, 33 | "cwd": "${workspaceFolder}/examples/build/", 34 | "environment": [], 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /examples/texture_arrays/non_uniform_indexing.wgsl: -------------------------------------------------------------------------------- 1 | struct FragmentInput { 2 | @location(0) tex_coord: vec2, 3 | @location(1) index: i32, 4 | } 5 | 6 | @group(0) @binding(0) 7 | var texture_array_top: binding_array>; 8 | @group(0) @binding(1) 9 | var texture_array_bottom: binding_array>; 10 | @group(0) @binding(2) 11 | var sampler_array: binding_array; 12 | 13 | @fragment 14 | fn non_uniform_main(fragment: FragmentInput) -> @location(0) vec4 { 15 | var outval: vec3; 16 | if fragment.tex_coord.y <= 0.5 { 17 | outval = textureSampleLevel( 18 | texture_array_top[fragment.index], 19 | sampler_array[fragment.index], 20 | fragment.tex_coord, 21 | 0.0 22 | ).rgb; 23 | } else { 24 | outval = textureSampleLevel( 25 | texture_array_bottom[fragment.index], 26 | sampler_array[fragment.index], 27 | fragment.tex_coord, 28 | 0.0 29 | ).rgb; 30 | } 31 | 32 | return vec4(outval, 1.0); 33 | } 34 | -------------------------------------------------------------------------------- /examples/build/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Application 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/textured_cube/textured_cube.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | modelViewProjectionMatrix: mat4x4f, 3 | } 4 | 5 | @binding(0) @group(0) var uniforms: Uniforms; 6 | 7 | struct VertexOutput { 8 | @builtin(position) Position: vec4f, 9 | @location(0) fragUV: vec2f, 10 | @location(1) fragPosition: vec4f, 11 | } 12 | 13 | @vertex 14 | fn vs_main( 15 | @location(0) position: vec4f, 16 | @location(1) uv: vec2f 17 | ) -> VertexOutput { 18 | var output: VertexOutput; 19 | output.Position = uniforms.modelViewProjectionMatrix * position; 20 | output.fragUV = uv; 21 | output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); 22 | return output; 23 | } 24 | 25 | @group(0) @binding(1) var mySampler: sampler; 26 | @group(0) @binding(2) var myTexture: texture_2d; 27 | 28 | @fragment 29 | fn fs_main( 30 | @location(0) fragUV: vec2f, 31 | @location(1) fragPosition: vec4f 32 | ) -> @location(0) vec4f { 33 | return textureSample(myTexture, mySampler, fragUV) * fragPosition; 34 | } 35 | -------------------------------------------------------------------------------- /utils/application/mouse_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package application 3 | 4 | // Vendor 5 | import "vendor:glfw" 6 | 7 | /* Updates the state of the mouse. */ 8 | mouse_update :: proc "contextless" (app := app_context) #no_bounds_check { 9 | copy(app.mouse.previous[:], app.mouse.current[:]) 10 | app.mouse.last_button_pressed = .Unknown 11 | 12 | // Update button states 13 | for button in Mouse_Button { 14 | if button == .Unknown { 15 | continue 16 | } 17 | state := glfw.GetMouseButton(window_get_handle(app.window), i32(button)) 18 | app.mouse.current[button] = state == glfw.PRESS 19 | if app.mouse.current[button] && !app.mouse.previous[button] { 20 | app.mouse.last_button_pressed = button 21 | } 22 | } 23 | 24 | // Update position 25 | app.mouse.previous_position = app.mouse.position 26 | app.mouse.position[0], app.mouse.position[1] = glfw.GetCursorPos(window_get_handle(app.window)) 27 | 28 | app.mouse.previous_scroll = app.mouse.scroll 29 | app.mouse.scroll = {0, 0} 30 | } 31 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial6_uniforms/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct CameraUniform { 4 | view_proj: mat4x4, 5 | }; 6 | @group(1) @binding(0) 7 | var camera: CameraUniform; 8 | 9 | struct VertexInput { 10 | @location(0) position: vec3, 11 | @location(1) tex_coords: vec2, 12 | } 13 | 14 | struct VertexOutput { 15 | @builtin(position) clip_position: vec4, 16 | @location(0) tex_coords: vec2, 17 | } 18 | 19 | @vertex 20 | fn vs_main( 21 | model: VertexInput, 22 | ) -> VertexOutput { 23 | var out: VertexOutput; 24 | out.tex_coords = model.tex_coords; 25 | out.clip_position = camera.view_proj * vec4(model.position, 1.0); 26 | return out; 27 | } 28 | 29 | // Fragment shader 30 | 31 | @group(0) @binding(0) 32 | var t_diffuse: texture_2d; 33 | @group(0)@binding(1) 34 | var s_diffuse: sampler; 35 | 36 | @fragment 37 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 38 | return textureSample(t_diffuse, s_diffuse, in.tex_coords); 39 | } 40 | -------------------------------------------------------------------------------- /examples/triangle/triangle.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) position: vec4f, 3 | @location(0) color: vec3f, 4 | } 5 | 6 | @vertex 7 | fn vs_main( 8 | @builtin(vertex_index) in_vertex_index: u32 9 | ) -> VertexOutput { 10 | var pos = array( 11 | vec2f( 0.0, 1.0), // Top vertex 12 | vec2f(-1.0, -1.0), // Bottom left vertex 13 | vec2f( 1.0, -1.0) // Bottom right vertex 14 | ); 15 | 16 | // Define colors for each vertex 17 | var colors = array( 18 | vec3f(1.0, 0.0, 0.0), // Red for top vertex 19 | vec3f(0.0, 1.0, 0.0), // Green for bottom left 20 | vec3f(0.0, 0.0, 1.0) // Blue for bottom right 21 | ); 22 | 23 | var output: VertexOutput; 24 | output.position = vec4f(pos[in_vertex_index], 0.0, 1.0); 25 | output.color = colors[in_vertex_index]; 26 | return output; 27 | } 28 | 29 | @fragment 30 | fn fs_main( 31 | @location(0) color: vec3f 32 | ) -> @location(0) vec4f { 33 | return vec4f(color, 1.0); 34 | } 35 | -------------------------------------------------------------------------------- /utils/application/video_mode.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import "base:runtime" 5 | 6 | Video_Mode :: struct { 7 | width: u32, 8 | height: u32, 9 | bits_per_pixel: u32, 10 | refresh_rate: u32, 11 | frame_time_target: f64, // in seconds 12 | } 13 | 14 | video_mode_is_valid :: proc (mode: Video_Mode) -> bool { 15 | runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() 16 | modes := get_video_modes(context.temp_allocator) 17 | for &curr_mode in modes { 18 | if video_mode_equals(curr_mode, mode) { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | 25 | video_mode_equals :: proc(left: Video_Mode, right: Video_Mode) -> bool { 26 | return left.width == right.width && 27 | left.height == right.height && 28 | left.refresh_rate == right.refresh_rate && 29 | left.bits_per_pixel == right.bits_per_pixel 30 | } 31 | 32 | video_mode_not_equals :: proc(left: Video_Mode, right: Video_Mode) -> bool { 33 | return !video_mode_equals(left, right) 34 | } 35 | -------------------------------------------------------------------------------- /examples/cube_textured/cube_textured.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @location(0) tex_coord: vec2, 3 | @builtin(position) position: vec4, 4 | }; 5 | 6 | @group(0) @binding(0) 7 | var transform: mat4x4; 8 | 9 | @vertex 10 | fn vs_main( 11 | @location(0) position: vec4, 12 | @location(1) tex_coord: vec2, 13 | ) -> VertexOutput { 14 | var result: VertexOutput; 15 | result.tex_coord = tex_coord; 16 | result.position = transform * position; 17 | return result; 18 | } 19 | 20 | @group(0) @binding(1) 21 | var r_color: texture_2d; 22 | 23 | @fragment 24 | fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { 25 | let tex = textureLoad(r_color, vec2(vertex.tex_coord * 256.0), 0); 26 | let v = f32(tex.x) / 255.0; 27 | let color = vec3(1.0 - (v * 5.0), 1.0 - (v * 15.0), 1.0 - (v * 50.0)); 28 | return vec4(color, 1.0); 29 | } 30 | 31 | @fragment 32 | fn fs_wire(vertex: VertexOutput) -> @location(0) vec4 { 33 | let color = vec3(0.0, 0.5, 0.0); 34 | return vec4(color, 0.5); 35 | } 36 | -------------------------------------------------------------------------------- /examples/image_blur/fullscreen_textured_quad.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var mySampler : sampler; 2 | @group(0) @binding(1) var myTexture : texture_2d; 3 | 4 | struct VertexOutput { 5 | @builtin(position) Position : vec4f, 6 | @location(0) fragUV : vec2f, 7 | } 8 | 9 | @vertex 10 | fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { 11 | var pos = array( 12 | vec2( 1.0, 1.0), 13 | vec2( 1.0, -1.0), 14 | vec2(-1.0, -1.0), 15 | vec2( 1.0, 1.0), 16 | vec2(-1.0, -1.0), 17 | vec2(-1.0, 1.0) 18 | ); 19 | 20 | var uv = array( 21 | vec2(1.0, 0.0), 22 | vec2(1.0, 1.0), 23 | vec2(0.0, 1.0), 24 | vec2(1.0, 0.0), 25 | vec2(0.0, 1.0), 26 | vec2(0.0, 0.0) 27 | ); 28 | 29 | var output : VertexOutput; 30 | output.Position = vec4(pos[VertexIndex], 0.0, 1.0); 31 | output.fragUV = uv[VertexIndex]; 32 | return output; 33 | } 34 | 35 | @fragment 36 | fn frag_main(@location(0) fragUV: vec2f) -> @location(0) vec4f { 37 | return textureSample(myTexture, mySampler, fragUV); 38 | } 39 | -------------------------------------------------------------------------------- /utils/imgui/imgui_impl_wgpu.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location(0) position: vec2, 3 | @location(1) uv: vec2, 4 | @location(2) color: vec4, 5 | }; 6 | 7 | struct VertexOutput { 8 | @builtin(position) position: vec4, 9 | @location(0) color: vec4, 10 | @location(1) uv: vec2, 11 | }; 12 | 13 | struct Uniforms { 14 | mvp: mat4x4, 15 | gamma: f32, 16 | }; 17 | 18 | @group(0) @binding(0) var uniforms: Uniforms; 19 | @group(0) @binding(1) var s: sampler; 20 | @group(1) @binding(0) var t: texture_2d; 21 | 22 | @vertex 23 | fn vs_main(in: VertexInput) -> VertexOutput { 24 | var out: VertexOutput; 25 | out.position = uniforms.mvp * vec4(in.position, 0.0, 1.0); 26 | out.color = in.color; 27 | out.uv = in.uv; 28 | return out; 29 | } 30 | 31 | @fragment 32 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 33 | let color = in.color * textureSample(t, s, in.uv); 34 | let corrected_color = pow(color.rgb, vec3(uniforms.gamma)); 35 | return vec4(corrected_color, color.a); 36 | } 37 | -------------------------------------------------------------------------------- /utils/application/keyboard.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | Keyboard_State :: struct { 4 | current: [KEY_COUNT]bool, 5 | previous: [KEY_COUNT]bool, 6 | last_key_pressed: Key, 7 | last_key_released: Key, 8 | key_repeat: bool, 9 | } 10 | 11 | key_is_pressed :: #force_inline proc "contextless" (key: Key, app := app_context) -> bool { 12 | return app.keyboard.current[key] && !app.keyboard.previous[key] 13 | } 14 | 15 | key_is_down :: #force_inline proc "contextless" (key: Key, app := app_context) -> bool { 16 | return app.keyboard.current[key] 17 | } 18 | 19 | key_is_released :: #force_inline proc "contextless" (key: Key, app := app_context) -> bool { 20 | return !app.keyboard.current[key] && app.keyboard.previous[key] 21 | } 22 | 23 | key_is_up :: #force_inline proc "contextless" (key: Key, app := app_context) -> bool { 24 | return !app.keyboard.current[key] 25 | } 26 | 27 | key_get_pressed :: #force_inline proc "contextless" (app := app_context) -> Key { 28 | return app.keyboard.last_key_pressed 29 | } 30 | 31 | set_exit_key :: proc "contextless" (key: Key, app := app_context) { 32 | app.exit_key = key 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Rafael Henrique Capati 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. 22 | -------------------------------------------------------------------------------- /examples/microui/README.md: -------------------------------------------------------------------------------- 1 | # MicroUI Example 2 | 3 | This example demonstrates how to use MicroUI with a WGPU-based renderer in Odin. 4 | 5 | ## Build 6 | 7 | To build the example, use the following command: 8 | 9 | ```shell 10 | odin build ./microui -out:./build/ 11 | ``` 12 | 13 | Replace `` with your desired output name. 14 | 15 | ## Usage 16 | 17 | The renderer utilities used in this example can be imported from the `utils/microui` folder. Here's a basic usage example: 18 | 19 | ```odin 20 | import mu "vendor:microui" 21 | import wmu "wgpu/utils/microui" 22 | 23 | // Initialize WGPU device, queue, and surface configuration 24 | // ... 25 | 26 | // Initialize the renderer 27 | mu_ctx := wmu.init(&device, &queue, &surface_config) 28 | 29 | // In your render loop: 30 | mu.begin(mu_ctx) 31 | // ... populate your MicroUI context ... 32 | mu.end(mu_ctx) 33 | 34 | // Render the UI 35 | wmu.render(&mu_ctx, &render_pass) 36 | 37 | // When resizing: 38 | wmu.resize(new_width, new_height) 39 | 40 | // Cleanup 41 | wmu.destroy() 42 | free(mu_ctx) 43 | ``` 44 | 45 | ## Screenshots 46 | 47 | ![MicroUI Example](./microui.png) 48 | -------------------------------------------------------------------------------- /examples/compute/compute.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) 2 | @binding(0) 3 | var v_indices: array; // this is used as both input and output for convenience 4 | 5 | // The Collatz Conjecture states that for any integer n: 6 | // If n is even, n = n/2 7 | // If n is odd, n = 3n+1 8 | // And repeat this process for each new n, you will always eventually reach 1. 9 | // Though the conjecture has not been proven, no counterexample has ever been found. 10 | // This function returns how many times this recurrence needs to be applied to reach 1. 11 | fn collatz_iterations(n_base: u32) -> u32{ 12 | var n: u32 = n_base; 13 | var i: u32 = 0u; 14 | loop { 15 | if (n <= 1u) { 16 | break; 17 | } 18 | if (n % 2u == 0u) { 19 | n = n / 2u; 20 | } 21 | else { 22 | // Overflow? (i.e. 3*n + 1 > 0xffffffffu?) 23 | if (n >= 1431655765u) { // 0x55555555u 24 | return 4294967295u; // 0xffffffffu 25 | } 26 | 27 | n = 3u * n + 1u; 28 | } 29 | i = i + 1u; 30 | } 31 | return i; 32 | } 33 | 34 | @compute 35 | @workgroup_size(1) 36 | fn main(@builtin(global_invocation_id) global_id: vec3) { 37 | v_indices[global_id.x] = collatz_iterations(v_indices[global_id.x]); 38 | } 39 | -------------------------------------------------------------------------------- /examples/triangle/triangle_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | package triangle 3 | 4 | // Core 5 | import "base:runtime" 6 | import "core:sys/wasm/js" 7 | 8 | // Local packages 9 | import wgpu "../.." 10 | 11 | OS :: struct { 12 | initialized: bool, 13 | } 14 | 15 | os_init :: proc() { 16 | ok := js.add_window_event_listener(.Resize, nil, size_callback) 17 | assert(ok) 18 | } 19 | 20 | // NOTE: frame loop is done by the runtime.js repeatedly calling `step`. 21 | os_run :: proc() { 22 | state.os.initialized = true 23 | } 24 | 25 | @(private="file", export) 26 | step :: proc(dt: f32) -> bool { 27 | if !state.os.initialized { 28 | return true 29 | } 30 | 31 | frame(dt) 32 | return true 33 | } 34 | 35 | os_get_framebuffer_size :: proc() -> (width, height: u32) { 36 | rect := js.get_bounding_client_rect("body") 37 | dpi := js.device_pixel_ratio() 38 | return u32(f64(rect.width) * dpi), u32(f64(rect.height) * dpi) 39 | } 40 | 41 | @(private="file", fini) 42 | os_fini :: proc "contextless" () { 43 | context = runtime.default_context() 44 | js.remove_window_event_listener(.Resize, nil, size_callback) 45 | 46 | finish() 47 | } 48 | 49 | @(private="file") 50 | size_callback :: proc(e: js.Event) { 51 | resize() 52 | } 53 | -------------------------------------------------------------------------------- /examples/instanced_cube/instanced.wgsl: -------------------------------------------------------------------------------- 1 | struct InstanceInput { 2 | @location(5) model_matrix_0: vec4, 3 | @location(6) model_matrix_1: vec4, 4 | @location(7) model_matrix_2: vec4, 5 | @location(8) model_matrix_3: vec4, 6 | }; 7 | 8 | struct VertexInput { 9 | @location(0) position : vec4f, 10 | @location(1) uv : vec2f, 11 | } 12 | 13 | struct VertexOutput { 14 | @builtin(position) Position : vec4f, 15 | @location(0) fragUV : vec2f, 16 | @location(1) fragPosition: vec4f, 17 | } 18 | 19 | @vertex 20 | fn vs_main( 21 | model: VertexInput, 22 | instance: InstanceInput, 23 | ) -> VertexOutput { 24 | let model_matrix = mat4x4( 25 | instance.model_matrix_0, 26 | instance.model_matrix_1, 27 | instance.model_matrix_2, 28 | instance.model_matrix_3, 29 | ); 30 | 31 | var output : VertexOutput; 32 | output.Position = model_matrix * model.position; 33 | output.fragUV = model.uv; 34 | output.fragPosition = 0.5 * (model.position + vec4(1.0)); 35 | return output; 36 | } 37 | 38 | @fragment 39 | fn fs_main( 40 | @location(0) fragUV: vec2f, 41 | @location(1) fragPosition: vec4f 42 | ) -> @location(0) vec4f { 43 | return fragPosition; 44 | } 45 | -------------------------------------------------------------------------------- /examples/fractal_cube/fractal_cube.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | modelViewProjectionMatrix: mat4x4, 3 | } 4 | 5 | @binding(0) @group(0) var uniforms: Uniforms; 6 | @binding(1) @group(0) var mySampler: sampler; 7 | @binding(2) @group(0) var myTexture: texture_2d; 8 | 9 | struct VertexOutput { 10 | @builtin(position) Position: vec4, 11 | @location(0) fragUV: vec2, 12 | @location(1) fragPosition: vec4, 13 | } 14 | 15 | @vertex 16 | fn vs_main( 17 | @location(0) position: vec4, 18 | @location(1) uv: vec2 19 | ) -> VertexOutput { 20 | var output: VertexOutput; 21 | output.Position = uniforms.modelViewProjectionMatrix * position; 22 | output.fragUV = uv; 23 | output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); 24 | return output; 25 | } 26 | 27 | @fragment 28 | fn fs_main( 29 | @location(0) fragUV: vec2, 30 | @location(1) fragPosition: vec4 31 | ) -> @location(0) vec4 { 32 | let texColor = textureSample(myTexture, mySampler, fragUV * 0.8 + vec2(0.1)); 33 | // The threshold of 0.01 used in the shader depends on the base gray color 34 | let f = select(1.0, 0.0, length(texColor.rgb - vec3(0.5)) < 0.01); 35 | return f * texColor + (1.0 - f) * fragPosition; 36 | } 37 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial1_window_sdl/tutorial1_window.odin: -------------------------------------------------------------------------------- 1 | package tutorial1_window_sdl 2 | 3 | // Core 4 | import "core:fmt" 5 | 6 | // Vendor 7 | import sdl "vendor:sdl2" 8 | 9 | main :: proc() { 10 | sdl_flags := sdl.InitFlags{.VIDEO, .EVENTS} 11 | 12 | if res := sdl.Init(sdl_flags); res != 0 { 13 | fmt.eprintf("ERROR: Failed to initialize SDL: [%s]\n", sdl.GetError()) 14 | return 15 | } 16 | defer sdl.Quit() 17 | 18 | window_flags: sdl.WindowFlags = {.SHOWN, .ALLOW_HIGHDPI, .RESIZABLE} 19 | 20 | sdl_window := sdl.CreateWindow( 21 | "Tutorial 1 - Window", 22 | sdl.WINDOWPOS_CENTERED, 23 | sdl.WINDOWPOS_CENTERED, 24 | 800, 25 | 600, 26 | window_flags, 27 | ) 28 | defer sdl.DestroyWindow(sdl_window) 29 | 30 | if sdl_window == nil { 31 | fmt.eprintf("ERROR: Failed to create the SDL Window: [%s]\n", sdl.GetError()) 32 | return 33 | } 34 | 35 | main_loop: for { 36 | e: sdl.Event 37 | 38 | for sdl.PollEvent(&e) { 39 | #partial switch (e.type) { 40 | case .QUIT: 41 | break main_loop 42 | 43 | case .WINDOWEVENT: 44 | #partial switch (e.window.event) { 45 | case .SIZE_CHANGED: 46 | case .RESIZED: 47 | } 48 | } 49 | } 50 | 51 | // We will render here... 52 | } 53 | 54 | fmt.println("Exiting...") 55 | } 56 | -------------------------------------------------------------------------------- /utils/application/video_mode_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package application 3 | 4 | // Vendor 5 | import "vendor:glfw" 6 | 7 | get_video_modes :: proc(allocator := context.allocator) -> (modes: []Video_Mode) { 8 | raw_modes := glfw.GetVideoModes(glfw.GetPrimaryMonitor()) 9 | count := len(raw_modes) 10 | 11 | if count == 0 { return {} } 12 | 13 | modes = make([]Video_Mode, count, allocator) 14 | 15 | for i in 0..< count { 16 | raw_mode := raw_modes[i] 17 | modes[i] = { 18 | width = u32(raw_mode.width), 19 | height = u32(raw_mode.height), 20 | bits_per_pixel = u32(raw_mode.red_bits + raw_mode.green_bits + raw_mode.blue_bits), 21 | refresh_rate = u32(raw_mode.refresh_rate), 22 | frame_time_target = 1.0 / f64(raw_mode.refresh_rate), 23 | } 24 | } 25 | 26 | return modes 27 | } 28 | 29 | get_video_mode :: proc() -> (mode: Video_Mode) { 30 | raw_mode := glfw.GetVideoMode(glfw.GetPrimaryMonitor()) 31 | mode = { 32 | width = u32(raw_mode.width), 33 | height = u32(raw_mode.height), 34 | bits_per_pixel = u32(raw_mode.red_bits + raw_mode.green_bits + raw_mode.blue_bits), 35 | refresh_rate = u32(raw_mode.refresh_rate), 36 | frame_time_target = 1.0 / f64(raw_mode.refresh_rate), 37 | } 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /utils/application/log.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import "core:fmt" 5 | import "core:log" 6 | import "core:strings" 7 | 8 | Log_Level :: enum { 9 | Info, // default 10 | Debug, 11 | Warn, 12 | Error, 13 | Fatal, 14 | } 15 | 16 | LOG_BUFFER_SIZE :: #config(APP_LOG_BUFFER_SIZE, 1024) 17 | 18 | log_loc :: proc(fmt_str: string, args: ..any, level := Log_Level.Info, loc := #caller_location) { 19 | buffer: [LOG_BUFFER_SIZE]byte 20 | builder := strings.builder_from_bytes(buffer[:]) 21 | 22 | // Write the formatted string first 23 | fmt.sbprintf(&builder, fmt_str, ..args) 24 | strings.write_byte(&builder, '\n') 25 | strings.write_string(&builder, " ") // 4 spaces for indentation 26 | 27 | // Write the location 28 | strings.write_string(&builder, loc.file_path) 29 | strings.write_byte(&builder, ':') 30 | strings.write_int(&builder, int(loc.line)) 31 | if loc.column != 0 { 32 | strings.write_byte(&builder, ':') 33 | strings.write_int(&builder, int(loc.column)) 34 | } 35 | 36 | str := strings.to_string(builder) 37 | switch level { 38 | case .Info: 39 | log.infof("%s", str) 40 | case .Debug: 41 | log.debugf("%s", str) 42 | case .Warn: 43 | log.warnf("%s", str) 44 | case .Error: 45 | log.errorf("%s", str) 46 | case .Fatal: 47 | log.fatalf("%s", str) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /utils/sdl3/surface_linux.odin: -------------------------------------------------------------------------------- 1 | #+build linux 2 | package wgpu_sdl3 3 | 4 | // Vendor 5 | import sdl "vendor:sdl3" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | get_surface_descriptor :: proc "c" (window: ^sdl.Window) -> (descriptor: wgpu.SurfaceDescriptor) { 11 | switch sdl.GetCurrentVideoDriver() { 12 | case "wayland": 13 | display := sdl.GetPointerProperty( 14 | sdl.GetWindowProperties(window), 15 | sdl.PROP_WINDOW_WAYLAND_DISPLAY_POINTER, 16 | nil, 17 | ) 18 | surface := sdl.GetPointerProperty( 19 | sdl.GetWindowProperties(window), 20 | sdl.PROP_WINDOW_WAYLAND_SURFACE_POINTER, 21 | nil, 22 | ) 23 | 24 | descriptor.target = wgpu.SurfaceSourceWaylandSurface { 25 | display = display, 26 | surface = surface, 27 | } 28 | case "x11": 29 | display := sdl.GetPointerProperty( 30 | sdl.GetWindowProperties(window), 31 | sdl.PROP_WINDOW_X11_DISPLAY_POINTER, 32 | nil, 33 | ) 34 | window := cast(u64)sdl.GetNumberProperty( 35 | sdl.GetWindowProperties(window), 36 | sdl.PROP_WINDOW_X11_WINDOW_NUMBER, 37 | 0, 38 | ) 39 | descriptor.target = wgpu.SurfaceSourceXlibWindow { 40 | display = display, 41 | window = window, 42 | } 43 | case: 44 | panic_contextless("Unsupported video driver, expected Wayland or X11") 45 | } 46 | 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /log.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package webgpu 3 | 4 | // Vendor 5 | import "vendor:wgpu" 6 | 7 | /* 8 | Defines the available log levels for WGPU logging, levels are ordered from least 9 | to most verbose. 10 | */ 11 | LogLevel :: wgpu.LogLevel 12 | 13 | /* 14 | Sets the global log level for WGPU. 15 | 16 | This controls which log messages will be emitted by the WGPU implementation. 17 | Only messages at the specified level and more severe levels will be shown. 18 | */ 19 | SetLogLevel :: wgpu.SetLogLevel 20 | 21 | /* 22 | Sets a callback procedure to handle WGPU log messages. 23 | 24 | The callback will be invoked whenever WGPU generates a log message at or above 25 | the current log level. Pass `nil` to remove the current callback. 26 | */ 27 | SetLogCallback :: wgpu.SetLogCallback 28 | 29 | /* 30 | Procedure type for WGPU logging callbacks. 31 | 32 | Inputs: 33 | 34 | - `level`: The severity level of the log message 35 | - `message`: The content of the log message as a string view 36 | - `userData`: Optional pointer to user-provided data that was passed when 37 | setting the callback 38 | */ 39 | LogCallback :: #type proc "c" (level: LogLevel, message: string, userData: rawptr) 40 | 41 | ConvertOdinToWGPULogLevel :: wgpu.ConvertOdinToWGPULogLevel 42 | ConvertWGPUToOdinLogLevel :: wgpu.ConvertWGPUToOdinLogLevel 43 | ConvertLogLevel :: wgpu.ConvertLogLevel 44 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run", 6 | "type": "shell", 7 | "windows": { 8 | "command": "build.bat ${input:exampleName} run" 9 | }, 10 | "linux": { 11 | "command": "./build.sh ${input:exampleName} run" 12 | }, 13 | "osx": { 14 | "command": "./build.sh ${input:exampleName} run" 15 | }, 16 | "options": { 17 | "cwd": "${workspaceFolder}/examples" 18 | }, 19 | "group": { 20 | "kind": "build", 21 | "isDefault": true 22 | }, 23 | "presentation": { 24 | "reveal": "always", 25 | "panel": "shared" 26 | }, 27 | "problemMatcher": [] 28 | } 29 | ], 30 | "inputs": [ 31 | { 32 | "id": "exampleName", 33 | "type": "pickString", 34 | "description": "Select the example to run", 35 | "options": [ 36 | "cameras", 37 | "capture", 38 | "clear_screen", 39 | "compute", 40 | "coordinate_system", 41 | "cube", 42 | "cube_textured", 43 | "cubemap", 44 | "fractal_cube", 45 | "image_blur", 46 | "imgui", 47 | "info", 48 | "instanced_cube", 49 | "microui", 50 | "rotating_cube", 51 | "square", 52 | "stencil_triangles", 53 | "texture_arrays", 54 | "textured_cube", 55 | "timestamp_query", 56 | "triangle", 57 | "triangle_msaa", 58 | "two_cubes", 59 | ] 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /pipeline_layout.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a pipeline layout. 8 | 9 | A `PipelineLayout` object describes the available binding groups of a pipeline. 10 | It can be created with `DeviceCreatePipelineLayout`. 11 | 12 | Corresponds to [WebGPU 13 | `GPUPipelineLayout`](https://gpuweb.github.io/gpuweb/#gpupipelinelayout). 14 | */ 15 | PipelineLayout :: wgpu.PipelineLayout 16 | 17 | /* Sets a debug label for the given `PipelineLayout`. */ 18 | PipelineLayoutSetLabel :: wgpu.PipelineLayoutSetLabel 19 | 20 | /* Increase the `PipelineLayout` reference count. */ 21 | PipelineLayoutAddRef :: #force_inline proc "c" (self: PipelineLayout) { 22 | wgpu.PipelineLayoutAddRef(self) 23 | } 24 | 25 | /* Release the `PipelineLayout` resources, use to decrease the reference count. */ 26 | PipelineLayoutRelease :: #force_inline proc "c" (self: PipelineLayout) { 27 | wgpu.PipelineLayoutRelease(self) 28 | } 29 | 30 | /* 31 | Safely releases the `PipelineLayout` resources and invalidates the handle. The 32 | procedure checks both the pointer and handle before releasing. 33 | 34 | Note: After calling this, the handle will be set to `nil` and should not be used. 35 | */ 36 | PipelineLayoutReleaseSafe :: proc "c" (self: ^PipelineLayout) { 37 | if self != nil && self^ != nil { 38 | wgpu.PipelineLayoutRelease(self^) 39 | self^ = nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /texture_view.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a texture view. 8 | 9 | A `TextureView` object describes a texture and associated metadata needed by a 10 | `RenderPipeline` or `BindGroup`. 11 | 12 | Corresponds to [WebGPU 13 | `GPUTextureView`](https://gpuweb.github.io/gpuweb/#gputextureview). 14 | */ 15 | TextureView :: wgpu.TextureView 16 | 17 | /* Sets a debug label for the given `TextureView`. */ 18 | TextureViewSetLabel :: #force_inline proc "c" (self: TextureView, label: string) { 19 | wgpu.TextureViewSetLabel(self, label) 20 | } 21 | 22 | /* Increase the `TextureView` reference count. */ 23 | TextureViewAddRef :: #force_inline proc "c" (self: TextureView) { 24 | wgpu.TextureViewAddRef(self) 25 | } 26 | 27 | /* Release the `TextureView` resources, use to decrease the reference count. */ 28 | TextureViewRelease :: #force_inline proc "c" (self: TextureView) { 29 | wgpu.TextureViewRelease(self) 30 | } 31 | 32 | /* 33 | Safely releases the `TextureView` resources and invalidates the handle. 34 | The procedure checks both the pointer and handle before releasing. 35 | 36 | Note: After calling this, the handle will be set to `nil` and should not be used. 37 | */ 38 | TextureViewReleaseSafe :: proc "c" (self: ^TextureView) { 39 | if self != nil && self^ != nil { 40 | wgpu.TextureViewRelease(self^) 41 | self^ = nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /range.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Core 4 | import intr "base:intrinsics" 5 | 6 | Range :: struct($T: typeid) where intr.type_is_ordered(T) { 7 | start, end: T, 8 | } 9 | 10 | RangeInit :: proc "contextless" ( 11 | $T: typeid, 12 | start, end: T, 13 | ) -> Range(T) where intr.type_is_ordered(T) { 14 | return Range(T){start, end} 15 | } 16 | 17 | /* Get the length of the Range */ 18 | RangeLen :: proc "contextless" (r: Range($T)) -> T { 19 | if range_is_empty(r) { 20 | return 0 21 | } 22 | return (r.end - r.start) + 1 23 | } 24 | 25 | /* Check if the range is empty */ 26 | RangeIsEmpty :: proc "contextless" (r: Range($T)) -> bool { 27 | return r.end < r.start 28 | } 29 | 30 | /* Check if a value is within the Range */ 31 | RangeContains :: proc "contextless" (r: Range($T), value: T) -> bool { 32 | return value >= r.start && value < r.end 33 | } 34 | 35 | /* Iterator for the Range */ 36 | RangeIterator :: struct($T: typeid) { 37 | current, end: T, 38 | } 39 | 40 | /* Create an iterator for the Range */ 41 | RangeCreateIterator :: proc "contextless" (r: Range($T)) -> RangeIterator(T) { 42 | return RangeIterator(T){r.start, r.end} 43 | } 44 | 45 | /* Get the next value from the iterator */ 46 | RangeIteratorNext :: proc "contextless" (it: ^RangeIterator($T), value: ^T) -> bool { 47 | if it.current < it.end { 48 | value^ = it.current 49 | it.current += 1 50 | return true 51 | } 52 | return false 53 | } 54 | -------------------------------------------------------------------------------- /query_set.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a query set. 8 | 9 | It can be created with `DeviceCreateQuerySet`. 10 | 11 | Corresponds to [WebGPU `GPUQuerySet`](https://gpuweb.github.io/gpuweb/#queryset). 12 | */ 13 | QuerySet :: wgpu.QuerySet 14 | 15 | /* Destroys the `QuerySet`. */ 16 | QuerySetDestroy :: wgpu.QuerySetDestroy 17 | 18 | /* Get the `QuerySet` count. */ 19 | QuerySetGetCount :: wgpu.QuerySetGetCount 20 | 21 | /* Get the `QuerySet` type. */ 22 | QuerySetGetType :: wgpu.QuerySetGetType 23 | 24 | /* Sets a debug label for the given `QuerySet`. */ 25 | QuerySetSetLabel :: wgpu.QuerySetSetLabel 26 | 27 | /* Increase the `QuerySet` reference count. */ 28 | QuerySetAddRef :: #force_inline proc "c" (self: QuerySet) { 29 | wgpu.QuerySetAddRef(self) 30 | } 31 | 32 | /* Release the `QuerySet` resources, use to decrease the reference count. */ 33 | QuerySetRelease :: #force_inline proc "c" (self: QuerySet) { 34 | wgpu.QuerySetRelease(self) 35 | } 36 | 37 | /* 38 | Safely releases the `QuerySet` resources and invalidates the handle. 39 | The procedure checks both the pointer and handle before releasing. 40 | 41 | Note: After calling this, the handle will be set to `nil` and should not be used. 42 | */ 43 | QuerySetReleaseSafe :: proc "c" (self: ^QuerySet) { 44 | if self != nil && self^ != nil { 45 | wgpu.QuerySetRelease(self^) 46 | self^ = nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /render_bundle.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Pre-prepared reusable bundle of GPU operations. 8 | 9 | It only supports a handful of render commands, but it makes them reusable. 10 | Executing a `RenderBundle` is often more efficient than issuing the underlying 11 | commands manually. 12 | 13 | It can be created by use of a `RenderBundleEncoder`, and executed onto a 14 | `Command_Encoder` using `RenderPassExecuteBundles`. 15 | */ 16 | RenderBundle :: wgpu.RenderBundle 17 | 18 | /* Sets a debug label for the given `RenderBundle`. */ 19 | RenderBundleSetLabel :: wgpu.RenderBundleSetLabel 20 | 21 | /* Increase the `RenderBundle` reference count. */ 22 | RenderBundleAddRef :: #force_inline proc "c" (self: RenderBundle) { 23 | wgpu.RenderBundleAddRef(self) 24 | } 25 | 26 | /* Release the `RenderBundle` resources, use to decrease the reference count. */ 27 | RenderBundleRelease :: #force_inline proc "c" (self: RenderBundle) { 28 | wgpu.RenderBundleRelease(self) 29 | } 30 | 31 | /* 32 | Safely releases the `RenderBundle` resources and invalidates the handle. 33 | The procedure checks both the pointer and handle before releasing. 34 | 35 | Note: After calling this, the handle will be set to `nil` and should not be used. 36 | */ 37 | RenderBundleReleaseSafe :: proc "c" (self: ^RenderBundle) { 38 | if self != nil && self^ != nil { 39 | wgpu.RenderBundleRelease(self^) 40 | self^ = nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/cube_textured/vertex.odin: -------------------------------------------------------------------------------- 1 | package cube_textured 2 | 3 | Vertex :: struct { 4 | position: [4]f32, 5 | tex_coords: [2]f32, 6 | } 7 | 8 | vertex_data := []Vertex { 9 | // top (0, 0, 1) 10 | vertex(-1, -1, 1, 0, 0), 11 | vertex(1, -1, 1, 1, 0), 12 | vertex(1, 1, 1, 1, 1), 13 | vertex(-1, 1, 1, 0, 1), 14 | // bottom (0, 0, -1) 15 | vertex(-1, 1, -1, 1, 0), 16 | vertex(1, 1, -1, 0, 0), 17 | vertex(1, -1, -1, 0, 1), 18 | vertex(-1, -1, -1, 1, 1), 19 | // right (1, 0, 0) 20 | vertex(1, -1, -1, 0, 0), 21 | vertex(1, 1, -1, 1, 0), 22 | vertex(1, 1, 1, 1, 1), 23 | vertex(1, -1, 1, 0, 1), 24 | // left (-1, 0, 0) 25 | vertex(-1, -1, 1, 1, 0), 26 | vertex(-1, 1, 1, 0, 0), 27 | vertex(-1, 1, -1, 0, 1), 28 | vertex(-1, -1, -1, 1, 1), 29 | // front (0, 1, 0) 30 | vertex(1, 1, -1, 1, 0), 31 | vertex(-1, 1, -1, 0, 0), 32 | vertex(-1, 1, 1, 0, 1), 33 | vertex(1, 1, 1, 1, 1), 34 | // back (0, -1, 0) 35 | vertex(1, -1, 1, 0, 0), 36 | vertex(-1, -1, 1, 1, 0), 37 | vertex(-1, -1, -1, 1, 1), 38 | vertex(1, -1, -1, 0, 1), 39 | } 40 | 41 | vertex :: proc "contextless" (pos1, pos2, pos3, tc1, tc2: f32) -> Vertex { 42 | return Vertex{position = {pos1, pos2, pos3, 1}, tex_coords = {tc1, tc2}} 43 | } 44 | 45 | index_data: []u16 = { 46 | 0 , 1, 2, 2, 3, 0, // top 47 | 4 , 5, 6, 6, 7, 4, // bottom 48 | 8 , 9, 10, 10, 11, 8, // right 49 | 12, 13, 14, 14, 15, 12, // left 50 | 16, 17, 18, 18, 19, 16, // front 51 | 20, 21, 22, 22, 23, 20, // back 52 | } 53 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial7_instancing/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | 3 | struct Camera { 4 | view_proj: mat4x4, 5 | } 6 | @group(1) @binding(0) 7 | var camera: Camera; 8 | 9 | struct VertexInput { 10 | @location(0) position: vec3, 11 | @location(1) tex_coords: vec2, 12 | } 13 | struct InstanceInput { 14 | @location(5) model_matrix_0: vec4, 15 | @location(6) model_matrix_1: vec4, 16 | @location(7) model_matrix_2: vec4, 17 | @location(8) model_matrix_3: vec4, 18 | } 19 | 20 | struct VertexOutput { 21 | @builtin(position) clip_position: vec4, 22 | @location(0) tex_coords: vec2, 23 | } 24 | 25 | @vertex 26 | fn vs_main( 27 | model: VertexInput, 28 | instance: InstanceInput, 29 | ) -> VertexOutput { 30 | let model_matrix = mat4x4( 31 | instance.model_matrix_0, 32 | instance.model_matrix_1, 33 | instance.model_matrix_2, 34 | instance.model_matrix_3, 35 | ); 36 | var out: VertexOutput; 37 | out.tex_coords = model.tex_coords; 38 | out.clip_position = camera.view_proj * model_matrix * vec4(model.position, 1.0); 39 | return out; 40 | } 41 | 42 | // Fragment shader 43 | 44 | @group(0) @binding(0) 45 | var t_diffuse: texture_2d; 46 | @group(0)@binding(1) 47 | var s_diffuse: sampler; 48 | 49 | @fragment 50 | fn fs_main(in: VertexOutput) -> @location(0) vec4 { 51 | return textureSample(t_diffuse, s_diffuse, in.tex_coords); 52 | } 53 | -------------------------------------------------------------------------------- /sampler.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a sampler. 8 | 9 | A `Sampler` object defines how a pipeline will sample from a `TextureView`. 10 | Samplers define image filters (including anisotropy) and address (wrapping) 11 | modes, among other things. See the documentation for `SamplerDescriptor` for 12 | more information. 13 | 14 | It can be created with `DeviceCreateSampler`. 15 | 16 | Corresponds to [WebGPU 17 | `GPUSampler`](https://gpuweb.github.io/gpuweb/#sampler-interface). 18 | */ 19 | Sampler :: wgpu.Sampler 20 | 21 | /* Sets a debug label for the given `Sampler`. */ 22 | SamplerSetLabel :: #force_inline proc "c" (self: Sampler, label: string) { 23 | wgpu.SamplerSetLabel(self, label) 24 | } 25 | 26 | /* Increase the `Sampler` reference count. */ 27 | SamplerAddRef :: #force_inline proc "c" (self: Sampler) { 28 | wgpu.SamplerAddRef(self) 29 | } 30 | 31 | /* Release the `Sampler` resources, use to decrease the reference count. */ 32 | SamplerRelease :: #force_inline proc "c" (self: Sampler) { 33 | wgpu.SamplerRelease(self) 34 | } 35 | 36 | /* 37 | Safely releases the `Sampler` resources and invalidates the handle. 38 | The procedure checks both the pointer and handle before releasing. 39 | 40 | Note: After calling this, the handle will be set to `nil` and should not be used. 41 | */ 42 | SamplerReleaseSafe :: proc "c" (self: ^Sampler) { 43 | if self != nil && self^ != nil { 44 | wgpu.SamplerRelease(self^) 45 | self^ = nil 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /utils/shaders/shaders.odin: -------------------------------------------------------------------------------- 1 | package wgpu_shader_utils 2 | 3 | // Core 4 | import "core:mem" 5 | import "core:strings" 6 | 7 | // Local packages 8 | import wgpu "../../" 9 | 10 | LINEAR_TO_SRGB_WGSL: string : #load("linear_to_srgb.wgsl", string) 11 | SRGB_TO_LINEAR_WGSL: string : #load("srgb_to_linear.wgsl", string) 12 | 13 | // odinfmt: disable 14 | SRGB_TO_LINEAR_COLOR_CONVERSION: string : SRGB_TO_LINEAR_WGSL + ` 15 | fn apply_color_conversion(color: vec3) -> vec3 { 16 | return srgb_to_linear(color); 17 | } 18 | ` 19 | 20 | LINEAR_TO_SRGB_COLOR_CONVERSION: string : LINEAR_TO_SRGB_WGSL + ` 21 | fn apply_color_conversion(color: vec3) -> vec3 { 22 | return linear_to_srgb(color); 23 | } 24 | ` 25 | 26 | NON_COLOR_CONVERSION: string : ` 27 | fn apply_color_conversion(color: vec3) -> vec3 { 28 | return color; 29 | } 30 | ` 31 | // odinfmt: enable 32 | 33 | apply_color_conversion :: proc( 34 | source: string, 35 | is_srgb: bool, 36 | allocator := context.allocator, 37 | loc := #caller_location, 38 | ) -> ( 39 | res: string, 40 | ok: bool, 41 | ) #optional_ok { 42 | err: mem.Allocator_Error 43 | 44 | if is_srgb { 45 | res, err = strings.join({SRGB_TO_LINEAR_COLOR_CONVERSION, source}, "\n", allocator) 46 | } else { 47 | res, err = strings.join({NON_COLOR_CONVERSION, source}, "\n", allocator) 48 | } 49 | 50 | if err != nil { 51 | wgpu.error_reset_and_update(err, "Failed to create the shader string", loc) 52 | return 53 | } 54 | 55 | return res, true 56 | } 57 | -------------------------------------------------------------------------------- /utils/application/run_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | package application 3 | 4 | // Core 5 | import "core:sys/wasm/js" 6 | 7 | @(private="file", export) 8 | step :: proc(dt: f32) -> bool { 9 | app := app_context 10 | if app == nil do return true 11 | context = app.custom_context 12 | if !app.prepared { 13 | return true 14 | } 15 | 16 | if !app.running { 17 | if app.callbacks.quit != nil { 18 | app.callbacks.quit(app) 19 | } 20 | return false 21 | } 22 | 23 | timer_begin_frame(&app.timer) 24 | 25 | // Application iteration 26 | if app.callbacks.step != nil { 27 | if !app.callbacks.step(app, f32(timer_get_delta(&app.timer))) { 28 | app.running = false 29 | } 30 | } 31 | 32 | // TODO: fix order 33 | keyboard_update() 34 | mouse_update() 35 | 36 | timer_end_frame(&app.timer) 37 | 38 | return app.running 39 | } 40 | 41 | run :: proc(app := app_context) -> (ok: bool) { 42 | assert(app != nil, "Invalid application") 43 | assert(!app.prepared, "Application already initialized") 44 | 45 | // Set up window callbacks 46 | _window_setup_callbacks(app.window) 47 | 48 | // Initialize the user application 49 | if app.callbacks.init != nil { 50 | if res := app.callbacks.init(app); !res { 51 | return 52 | } 53 | } 54 | 55 | timer_init(&app.timer, 0, 0) 56 | 57 | app.prepared = true 58 | app.running = true 59 | 60 | return true 61 | } 62 | 63 | @(private="file", fini) 64 | _js_fini :: proc "contextless" () { 65 | app := app_context 66 | context = app.custom_context 67 | destroy(app) 68 | } 69 | -------------------------------------------------------------------------------- /bind_group.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a binding group. 8 | 9 | A `BindGroup` represents the set of resources bound to the bindings described by 10 | a `BindGroupLayout`. It can be created with `DeviceCreateBindGroup`. A 11 | `BindGroup` can be bound to a particular `RenderPass` with 12 | `RenderPassSetBindGroup`, or to a `ComputePass` with `ComputePassSetBindGroup`. 13 | 14 | Corresponds to [WebGPU 15 | `GPUBindGroup`](https://gpuweb.github.io/gpuweb/#gpubindgroup). 16 | */ 17 | BindGroup :: wgpu.BindGroup 18 | 19 | /* Sets a debug label for the given `BindGroup`. */ 20 | BindGroupSetLabel :: #force_inline proc "c" (self: BindGroup, label: string) { 21 | wgpu.BindGroupSetLabel(self, label) 22 | } 23 | 24 | /* Increase reference count. */ 25 | BindGroupAddRef :: #force_inline proc "c" (self: BindGroup) { 26 | wgpu.BindGroupAddRef(self) 27 | } 28 | 29 | /* Release resources, use to decrease the reference count. */ 30 | BindGroupRelease :: #force_inline proc "c" (self: BindGroup) { 31 | wgpu.BindGroupRelease(self) 32 | } 33 | 34 | /* 35 | Safely releases the `BindGroup` resources and invalidates the handle. 36 | The procedure checks both the pointer validity and the Bind group handle before releasing. 37 | 38 | Note: After calling this, the Bind group handle will be set to `nil` and should not be used. 39 | */ 40 | BindGroupReleaseSafe :: proc "c" (self: ^BindGroup) { 41 | if self != nil && self^ != nil { 42 | wgpu.BindGroupRelease(self^) 43 | self^ = nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /render_bundle_encoder_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package webgpu 3 | 4 | // Vendor 5 | import "vendor:wgpu" 6 | 7 | /* 8 | Set push constant data. 9 | 10 | Offset is measured in bytes, but must be a multiple of `PUSH_CONSTANT_ALIGNMENT`. 11 | 12 | Data size must be a multiple of `4` and must have an alignment of `4`. For 13 | example, with an offset of `4` and an array of `[8]u8`, that will write to the 14 | range of `4..12`. 15 | 16 | For each byte in the range of push constant data written, the union of the 17 | stages of all push constant ranges that covers that byte must be exactly 18 | `stages`. There's no good way of explaining this simply, so here are some 19 | examples: 20 | 21 | ```text 22 | For the given ranges: 23 | - 0..4 Vertex 24 | - 4..8 Fragment 25 | ``` 26 | 27 | You would need to upload this in two `RenderBundleEncoderSetPushConstants` 28 | calls. First for the `Vertex` range, second for the `Fragment` range. 29 | 30 | ```text 31 | For the given ranges: 32 | - 0..8 Vertex 33 | - 4..12 Fragment 34 | ``` 35 | 36 | You would need to upload this in three 37 | `RenderBundleEncoderSetPushConstants` calls. First for the `Vertex` only 38 | range `0..4`, second for the `{.Vertex, .Fragment}` range `4..8`, third for the 39 | `Fragment` range `8..12`. 40 | */ 41 | RenderBundleEncoderSetPushConstants :: proc "c" ( 42 | self: RenderBundleEncoder, 43 | stages: ShaderStages, 44 | offset: u32, 45 | data: []byte, 46 | ) { 47 | wgpu.RenderBundleEncoderSetPushConstants(self, stages, offset, u32(len(data)), raw_data(data)) 48 | } 49 | -------------------------------------------------------------------------------- /examples/fractal_cube/cube.odin: -------------------------------------------------------------------------------- 1 | package fractal_cube 2 | 3 | Vertex :: struct { 4 | position: [4]f32, 5 | tex_coords: [2]f32, 6 | } 7 | 8 | CUBE_VERTEX_DATA := []Vertex { 9 | // top (0, 0, 1) 10 | vertex(-1, -1, 1, 0, 0), 11 | vertex( 1, -1, 1, 1, 0), 12 | vertex( 1, 1, 1, 1, 1), 13 | vertex(-1, 1, 1, 0, 1), 14 | // bottom (0, 0, -1) 15 | vertex(-1, 1, -1, 1, 0), 16 | vertex( 1, 1, -1, 0, 0), 17 | vertex( 1, -1, -1, 0, 1), 18 | vertex(-1, -1, -1, 1, 1), 19 | // right (1, 0, 0) 20 | vertex( 1, -1, -1, 0, 0), 21 | vertex( 1, 1, -1, 1, 0), 22 | vertex( 1, 1, 1, 1, 1), 23 | vertex( 1, -1, 1, 0, 1), 24 | // left (-1, 0, 0) 25 | vertex(-1, -1, 1, 1, 0), 26 | vertex(-1, 1, 1, 0, 0), 27 | vertex(-1, 1, -1, 0, 1), 28 | vertex(-1, -1, -1, 1, 1), 29 | // front (0, 1, 0) 30 | vertex( 1, 1, -1, 1, 0), 31 | vertex(-1, 1, -1, 0, 0), 32 | vertex(-1, 1, 1, 0, 1), 33 | vertex( 1, 1, 1, 1, 1), 34 | // back (0, -1, 0) 35 | vertex( 1, -1, 1, 0, 0), 36 | vertex(-1, -1, 1, 1, 0), 37 | vertex(-1, -1, -1, 1, 1), 38 | vertex( 1, -1, -1, 0, 1), 39 | } 40 | 41 | vertex :: proc "contextless" (pos1, pos2, pos3, tc1, tc2: f32) -> Vertex { 42 | return Vertex { 43 | position = {pos1, pos2, pos3, 1}, 44 | tex_coords = {tc1, tc2}, 45 | } 46 | } 47 | 48 | CUBE_INDICES_DATA :: []u16 { 49 | 0 , 1, 2, 2, 3, 0, // top 50 | 4 , 5, 6, 6, 7, 4, // bottom 51 | 8 , 9, 10, 10, 11, 8, // right 52 | 12, 13, 14, 14, 15, 12, // left 53 | 16, 17, 18, 18, 19, 16, // front 54 | 20, 21, 22, 22, 23, 20, // back 55 | } 56 | -------------------------------------------------------------------------------- /examples/cubemap/cubemap.wgsl: -------------------------------------------------------------------------------- 1 | struct Uniforms { 2 | modelViewProjectionMatrix : mat4x4f, 3 | } 4 | @binding(0) @group(0) var uniforms : Uniforms; 5 | 6 | struct VertexOutput { 7 | @builtin(position) Position : vec4f, 8 | @location(0) fragUV: vec2f, 9 | @location(1) fragPosition: vec4f, 10 | } 11 | 12 | @vertex 13 | fn vs_main( 14 | @location(0) position : vec4f, 15 | @location(1) uv : vec2f 16 | ) -> VertexOutput { 17 | var output : VertexOutput; 18 | output.Position = uniforms.modelViewProjectionMatrix * position; 19 | output.fragUV = uv; 20 | output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); 21 | return output; 22 | } 23 | 24 | @group(0) @binding(1) var mySampler: sampler; 25 | @group(0) @binding(2) var myTexture: texture_cube; 26 | 27 | @fragment 28 | fn fs_main( 29 | @location(0) fragUV: vec2f, 30 | @location(1) fragPosition: vec4f 31 | ) -> @location(0) vec4f { 32 | // Our camera and the skybox cube are both centered at (0, 0, 0) 33 | // so we can use the cube geometry position to get viewing vector to sample 34 | // the cube texture. The magnitude of the vector doesn't matter. 35 | var cubemapVec = fragPosition.xyz - vec3(0.5); 36 | 37 | // When viewed from the inside, cubemaps are left-handed (z away from viewer), 38 | // but common camera matrix convention results in a right-handed world space 39 | // (z toward viewer), so we have to flip it. 40 | cubemapVec.z = -cubemapVec.z; 41 | 42 | return textureSample(myTexture, mySampler, cubemapVec); 43 | } 44 | -------------------------------------------------------------------------------- /command_buffer.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a command buffer on the GPU. 8 | 9 | A `CommandBuffer` represents a complete sequence of commands that may be 10 | submitted to a command queue with `QueueSubmit`. A `CommandBuffer` is obtained 11 | by recording a series of commands to a `CommandEncoder` and then calling 12 | `CommandEncoderFinish`. 13 | 14 | Corresponds to [WebGPU 15 | `GPUCommandBuffer`](https://gpuweb.github.io/gpuweb/#command-buffer). 16 | */ 17 | CommandBuffer :: wgpu.CommandBuffer 18 | 19 | /* Sets a debug label for the given `CommandBuffer`. */ 20 | CommandBufferSetLabel :: #force_inline proc "c" (self: CommandBuffer, label: string) { 21 | wgpu.CommandBufferSetLabel(self, label) 22 | } 23 | 24 | /* Increase the `CommandBuffer` reference count. */ 25 | CommandBufferAddRef :: #force_inline proc "c" (self: CommandBuffer) { 26 | wgpu.CommandBufferAddRef(self) 27 | } 28 | 29 | /* Release the `CommandBuffer` resources, use to decrease the reference count. */ 30 | CommandBufferRelease :: #force_inline proc "c" (self: CommandBuffer) { 31 | wgpu.CommandBufferRelease(self) 32 | } 33 | 34 | /* 35 | Safely releases the `CommandBuffer` resources and invalidates the handle. The 36 | procedure checks both the pointer validity and Command buffer handle before releasing. 37 | 38 | Note: After calling this, Command buffer handle will be set to `nil` and should not be used. 39 | */ 40 | CommandBufferReleaseSafe :: proc "c" (self: ^CommandBuffer) { 41 | if self != nil && self^ != nil { 42 | wgpu.CommandBufferRelease(self^) 43 | self^ = nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /device_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package webgpu 3 | 4 | // Vendor 5 | import "vendor:wgpu" 6 | 7 | /* 8 | Check for resource cleanups and mapping callbacks. Will block if `wait` is `true`. 9 | 10 | Return `true` if the queue is empty, or `false` if there are more queue 11 | submissions still in flight. (Note that, unless access to the [`Queue`] is 12 | coordinated somehow, this information could be out of date by the time the 13 | caller receives it. `Queue`s can be shared between threads, so other threads 14 | could submit new work at any time.) 15 | 16 | When running on WebGPU, this is a no-op. `Device`s are automatically polled. 17 | */ 18 | DevicePoll :: proc "c" ( 19 | self: Device, 20 | wait: bool = true, 21 | submissionIndex: ^SubmissionIndex = nil, 22 | ) -> bool { 23 | return bool(wgpu.DevicePoll(self, b32(wait), submissionIndex)) 24 | } 25 | 26 | ShaderModuleDescriptorSpirV :: struct { 27 | label: string, 28 | sources: []u32, 29 | } 30 | 31 | /* Creates a shader module from SPIR-V binary directly. */ 32 | @(require_results) 33 | DeviceCreateShaderModuleSpirV :: proc "c" ( 34 | self: Device, 35 | descriptor: ShaderModuleDescriptorSpirV, 36 | ) -> ( 37 | shaderModule: ShaderModule, 38 | ) { 39 | assert_contextless(descriptor.sources != nil && len(descriptor.sources) > 0, 40 | "SPIR-V source is required") 41 | 42 | raw_desc: wgpu.ShaderModuleDescriptorSpirV 43 | raw_desc.label = descriptor.label 44 | raw_desc.sourceSize = cast(u32)len(descriptor.sources) 45 | raw_desc.source = raw_data(descriptor.sources) 46 | 47 | shaderModule = wgpu.DeviceCreateShaderModuleSpirV(self, &raw_desc) 48 | 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /examples/textured_cube/cube.odin: -------------------------------------------------------------------------------- 1 | package rotating_cube_textured 2 | 3 | Vertex :: struct { 4 | position: [4]f32, 5 | tex_coords: [2]f32, 6 | } 7 | 8 | CUBE_VERTEX_DATA := []Vertex { 9 | // top (0, 0, 1) 10 | vertex(-1, -1, 1, 0, 0), 11 | vertex( 1, -1, 1, 1, 0), 12 | vertex( 1, 1, 1, 1, 1), 13 | vertex(-1, 1, 1, 0, 1), 14 | // bottom (0, 0, -1) 15 | vertex(-1, 1, -1, 1, 0), 16 | vertex( 1, 1, -1, 0, 0), 17 | vertex( 1, -1, -1, 0, 1), 18 | vertex(-1, -1, -1, 1, 1), 19 | // right (1, 0, 0) 20 | vertex( 1, -1, -1, 0, 0), 21 | vertex( 1, 1, -1, 1, 0), 22 | vertex( 1, 1, 1, 1, 1), 23 | vertex( 1, -1, 1, 0, 1), 24 | // left (-1, 0, 0) 25 | vertex(-1, -1, 1, 1, 0), 26 | vertex(-1, 1, 1, 0, 0), 27 | vertex(-1, 1, -1, 0, 1), 28 | vertex(-1, -1, -1, 1, 1), 29 | // front (0, 1, 0) 30 | vertex( 1, 1, -1, 1, 0), 31 | vertex(-1, 1, -1, 0, 0), 32 | vertex(-1, 1, 1, 0, 1), 33 | vertex( 1, 1, 1, 1, 1), 34 | // back (0, -1, 0) 35 | vertex( 1, -1, 1, 0, 0), 36 | vertex(-1, -1, 1, 1, 0), 37 | vertex(-1, -1, -1, 1, 1), 38 | vertex( 1, -1, -1, 0, 1), 39 | } 40 | 41 | vertex :: proc "contextless" (pos1, pos2, pos3, tc1, tc2: f32) -> Vertex { 42 | return Vertex{position = {pos1, pos2, pos3, 1}, tex_coords = {tc1, tc2}} 43 | } 44 | 45 | CUBE_INDICES_DATA :: []u16 { 46 | 0 , 1, 2, 2, 3, 0, // top 47 | 4 , 5, 6, 6, 7, 4, // bottom 48 | 8 , 9, 10, 10, 11, 8, // right 49 | 12, 13, 14, 14, 15, 12, // left 50 | 16, 17, 18, 18, 19, 16, // front 51 | 20, 21, 22, 22, 23, 20, // back 52 | } 53 | -------------------------------------------------------------------------------- /examples/texture_arrays/indexing.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | @location(0) position: vec2, 3 | @location(1) tex_coord: vec2, 4 | @location(2) index: i32, 5 | } 6 | 7 | struct VertexOutput { 8 | @builtin(position) position: vec4, 9 | @location(0) tex_coord: vec2, 10 | @location(1) index: i32, 11 | } 12 | 13 | @vertex 14 | fn vert_main(vertex: VertexInput) -> VertexOutput { 15 | var outval: VertexOutput; 16 | outval.position = vec4(vertex.position.x, vertex.position.y, 0.0, 1.0); 17 | outval.tex_coord = vertex.tex_coord; 18 | outval.index = vertex.index; 19 | return outval; 20 | } 21 | 22 | struct FragmentInput { 23 | @location(0) tex_coord: vec2, 24 | @location(1) index: i32, 25 | } 26 | 27 | @group(0) @binding(0) 28 | var texture_array_top: binding_array>; 29 | @group(0) @binding(1) 30 | var texture_array_bottom: binding_array>; 31 | @group(0) @binding(2) 32 | var sampler_array: binding_array; 33 | 34 | struct Uniforms { 35 | index: u32, 36 | } 37 | 38 | @group(0) @binding(3) 39 | var uniforms: Uniforms; 40 | 41 | @fragment 42 | fn uniform_main(fragment: FragmentInput) -> @location(0) vec4 { 43 | var outval: vec3; 44 | if fragment.tex_coord.y <= 0.5 { 45 | outval = textureSampleLevel( 46 | texture_array_top[uniforms.index], 47 | sampler_array[uniforms.index], 48 | fragment.tex_coord, 49 | 0.0 50 | ).rgb; 51 | } else { 52 | outval = textureSampleLevel( 53 | texture_array_bottom[uniforms.index], 54 | sampler_array[uniforms.index], 55 | fragment.tex_coord, 56 | 0.0 57 | ).rgb; 58 | } 59 | 60 | return vec4(outval, 1.0); 61 | } 62 | -------------------------------------------------------------------------------- /utils/application/key.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | Key :: enum { 4 | Unknown, 5 | Space, 6 | Apostrophe, 7 | Comma, 8 | Minus, 9 | Period, 10 | Slash, 11 | Semicolon, 12 | Equal, 13 | Left_Bracket, 14 | Backslash, 15 | Right_Bracket, 16 | Grave_Accent, 17 | World1, 18 | World2, 19 | N0, 20 | N1, 21 | N2, 22 | N3, 23 | N4, 24 | N5, 25 | N6, 26 | N7, 27 | N8, 28 | N9, 29 | A, 30 | B, 31 | C, 32 | D, 33 | E, 34 | F, 35 | G, 36 | H, 37 | I, 38 | J, 39 | K, 40 | L, 41 | M, 42 | N, 43 | O, 44 | P, 45 | Q, 46 | R, 47 | S, 48 | T, 49 | U, 50 | V, 51 | W, 52 | X, 53 | Y, 54 | Z, 55 | Escape, 56 | Enter, 57 | Tab, 58 | Backspace, 59 | Insert, 60 | Delete, 61 | Right, 62 | Left, 63 | Down, 64 | Up, 65 | Page_Up, 66 | Page_Down, 67 | Home, 68 | End, 69 | Caps_Lock, 70 | Scroll_Lock, 71 | Num_Lock, 72 | Print_Screen, 73 | Pause, 74 | F1, 75 | F2, 76 | F3, 77 | F4, 78 | F5, 79 | F6, 80 | F7, 81 | F8, 82 | F9, 83 | F10, 84 | F11, 85 | F12, 86 | F13, 87 | F14, 88 | F15, 89 | F16, 90 | F17, 91 | F18, 92 | F19, 93 | F20, 94 | F21, 95 | F22, 96 | F23, 97 | F24, 98 | F25, 99 | Kp0, 100 | Kp1, 101 | Kp2, 102 | Kp3, 103 | Kp4, 104 | Kp5, 105 | Kp6, 106 | Kp7, 107 | Kp8, 108 | Kp9, 109 | Kp_Decimal, 110 | Kp_Divide, 111 | Kp_Multiply, 112 | Kp_Subtract, 113 | Kp_Add, 114 | Kp_Enter, 115 | Kp_Equal, 116 | Left_Shift, 117 | Left_Control, 118 | Left_Alt, 119 | Left_Super, 120 | Right_Shift, 121 | Right_Control, 122 | Right_Alt, 123 | Right_Super, 124 | Menu, 125 | Last, 126 | } 127 | 128 | KEY_COUNT :: Key.Last 129 | 130 | Scancode :: distinct int 131 | -------------------------------------------------------------------------------- /examples/info/info.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package info 3 | 4 | // Core 5 | import "base:runtime" 6 | import "core:fmt" 7 | 8 | // Local packages 9 | import wgpu "../../" 10 | 11 | main :: proc() { 12 | wgpu_version := wgpu.GetVersion() 13 | 14 | fmt.printf( 15 | "WGPU version: %d.%d.%d.%d\n\n", 16 | wgpu_version.major, 17 | wgpu_version.minor, 18 | wgpu_version.patch, 19 | wgpu_version.build, 20 | ) 21 | 22 | instance := wgpu.CreateInstance() 23 | defer wgpu.Release(instance) 24 | 25 | runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() 26 | ta := context.temp_allocator 27 | 28 | adapters := wgpu.InstanceEnumerateAdapters(instance, wgpu.BACKENDS_ALL, ta) 29 | if len(adapters) == 0 { 30 | fmt.eprintln("No adapters available") 31 | return 32 | } 33 | 34 | fmt.println("Available adapter(s):\n") 35 | 36 | for a, i in adapters { 37 | info, status := wgpu.AdapterGetInfo(a) 38 | if status != .Success { 39 | fmt.eprintfln("Failed to get adapter info at index [%d]", i) 40 | continue 41 | } 42 | wgpu.AdapterInfoPrint(info) 43 | wgpu.AdapterInfoFreeMembers(info) 44 | } 45 | 46 | adapter_res := wgpu.InstanceRequestAdapterSync(instance) 47 | if (adapter_res.status != .Success) { 48 | fmt.eprintfln( 49 | "Failed to request the selected adapter [%v]: %s", 50 | adapter_res.status, 51 | adapter_res.message, 52 | ) 53 | return 54 | } 55 | 56 | adapter := adapter_res.adapter 57 | defer wgpu.Release(adapter) 58 | 59 | fmt.println("\nSelected adapter:\n") 60 | info, status := wgpu.AdapterGetInfo(adapter) 61 | if status != .Success { 62 | fmt.eprintln("Failed to get adapter info for the selected adapter") 63 | return 64 | } 65 | wgpu.AdapterInfoPrint(info) 66 | wgpu.AdapterInfoFreeMembers(info) 67 | } 68 | -------------------------------------------------------------------------------- /compute_pass_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package webgpu 3 | 4 | // Vendor 5 | import "vendor:wgpu" 6 | 7 | /* 8 | Set push constant data for subsequent dispatch calls. 9 | 10 | Write the bytes in `data` at offset `offset` within push constant storage. Both 11 | `offset` and the length of `data` must be multiples of 12 | `PUSH_CONSTANT_ALIGNMENT`, which is always 4. 13 | 14 | For example, if `offset` is `4` and `data` is eight bytes long, this call will 15 | write `data` to bytes `4..12` of push constant storage. 16 | */ 17 | ComputePassSetPushConstants :: proc "c" (self: ComputePass, offset: u32, data: []byte) { 18 | wgpu.ComputePassEncoderSetPushConstants(self, offset, u32(len(data)), raw_data(data)) 19 | } 20 | 21 | /* 22 | Issue a timestamp command at this point in the queue. The timestamp will be 23 | written to the specified query set, at the specified index. 24 | 25 | Must be multiplied by `queue_get_timestamp_period` to get the value in 26 | nanoseconds. Absolute values have no meaning, but timestamps can be subtracted 27 | to get the time it takes for a string of operations to complete. 28 | */ 29 | ComputePassWriteTimestamp :: wgpu.ComputePassEncoderWriteTimestamp 30 | 31 | /* 32 | Start a pipeline statistics query on this compute pass. It can be ended with 33 | `compute_pass_end_pipeline_statistics_query`. Pipeline statistics queries may 34 | not be nested. 35 | */ 36 | ComputePassBeginPipelineStatisticsQuery :: wgpu.ComputePassEncoderBeginPipelineStatisticsQuery 37 | 38 | /* 39 | End the pipeline statistics query on this compute pass. It can be started with 40 | `compute_pass_begin_pipeline_statistics_query`. Pipeline statistics queries may 41 | not be nested. 42 | */ 43 | ComputePassEndPipelineStatisticsQuery :: wgpu.ComputePassEncoderEndPipelineStatisticsQuery 44 | -------------------------------------------------------------------------------- /bind_group_layout.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a binding group layout. 8 | 9 | A `BindGroupLayout` is a handle to the GPU-side layout of a binding group. It 10 | can be used to create a `BindGroupDescriptor` object, which in turn can be used 11 | to create a `BindGroup` object with `DeviceCreateBindGroup`. A series of 12 | `BindGroupLayout`s can also be used to create a `PipelineLayoutDescriptor`, 13 | which can be used to create a `PipelineLayout`. 14 | 15 | It can be created with `DeviceCreateBindGroupLayout`. 16 | 17 | Corresponds to [WebGPU `GPUBindGroupLayout`]( 18 | https://gpuweb.github.io/gpuweb/#gpubindgrouplayout). 19 | */ 20 | BindGroupLayout :: wgpu.BindGroupLayout 21 | 22 | /* Sets a debug label for the given `BindGroupLayout`. */ 23 | BindGroupLayoutSetLabel :: #force_inline proc "c" (self: BindGroupLayout, label: cstring) { 24 | wgpu.BindGroupLayoutSetLabel(self, label) 25 | } 26 | 27 | /* Increase the reference count. */ 28 | BindGroupLayoutAddRef :: #force_inline proc "c" (self: BindGroupLayout) { 29 | wgpu.BindGroupLayoutAddRef(self) 30 | } 31 | 32 | /* Release resources, use to decrease the reference count. */ 33 | BindGroupLayoutRelease :: #force_inline proc "c" (self: BindGroupLayout) { 34 | wgpu.BindGroupLayoutRelease(self) 35 | } 36 | 37 | /* 38 | Safely releases the `BindGroupLayout` resources and invalidates the handle. The 39 | procedure checks both the pointer validity and the Bind group layout handle 40 | before releasing. 41 | 42 | Note: After calling this, the Bind group layout handle will be set to `nil` and 43 | should not be used. 44 | */ 45 | BindGroupLayoutReleaseSafe :: proc "c" (self: ^BindGroupLayout) { 46 | if self != nil && self^ != nil { 47 | wgpu.BindGroupLayoutRelease(self^) 48 | self^ = nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/triangle/triangle_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package triangle 3 | 4 | // Core 5 | import "core:time" 6 | 7 | // Vendor 8 | import "vendor:glfw" 9 | 10 | CLIENT_WIDTH :: 640 11 | CLIENT_HEIGHT :: 480 12 | 13 | OS :: struct { 14 | window: glfw.WindowHandle, 15 | minimized: bool, 16 | } 17 | 18 | os_init :: proc() { 19 | // Initialize GLFW library 20 | ensure(bool(glfw.Init()), "Failed to initialize GLFW") 21 | 22 | // Ensure no OpenGL context is loaded before window creation 23 | glfw.WindowHint_int(glfw.CLIENT_API, glfw.NO_API) 24 | 25 | // Create a window with the given size and title 26 | state.os.window = glfw.CreateWindow(CLIENT_WIDTH, CLIENT_HEIGHT, EXAMPLE_TITLE, nil, nil) 27 | assert(state.os.window != nil, "Failed to create window") 28 | 29 | glfw.SetFramebufferSizeCallback(state.os.window, size_callback) 30 | glfw.SetWindowIconifyCallback(state.os.window, minimize_callback) 31 | } 32 | 33 | os_run :: proc() { 34 | dt: f32 35 | 36 | for !glfw.WindowShouldClose(state.os.window) { 37 | start := time.tick_now() 38 | 39 | glfw.PollEvents() 40 | if !state.os.minimized { 41 | frame(dt) 42 | } else { 43 | time.sleep(100 * time.Millisecond) 44 | continue 45 | } 46 | 47 | dt = f32(time.duration_seconds(time.tick_since(start))) 48 | } 49 | 50 | finish() 51 | 52 | glfw.DestroyWindow(state.os.window) 53 | glfw.Terminate() 54 | } 55 | 56 | os_get_framebuffer_size :: proc() -> (width, height: u32) { 57 | iw, ih := glfw.GetFramebufferSize(state.os.window) 58 | return u32(iw), u32(ih) 59 | } 60 | 61 | size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) { 62 | if state.os.minimized do return 63 | resize() 64 | } 65 | 66 | minimize_callback :: proc "c" (window: glfw.WindowHandle, iconified: i32) { 67 | context = state.ctx 68 | state.os.minimized = bool(iconified) 69 | } 70 | -------------------------------------------------------------------------------- /examples/cube/vertex.odin: -------------------------------------------------------------------------------- 1 | package cube_example 2 | 3 | Vertex :: struct { 4 | position: [3]f32, 5 | color: [3]f32, 6 | } 7 | 8 | vertex_data := []Vertex { 9 | // +X Face - red 10 | vertex(0.5, -0.5, 0.5, 1, 0, 0), 11 | vertex(0.5, -0.5, -0.5, 1, 0, 0), 12 | vertex(0.5, 0.5, -0.5, 1, 0, 0), 13 | vertex(0.5, -0.5, 0.5, 1, 0, 0), 14 | vertex(0.5, 0.5, -0.5, 1, 0, 0), 15 | vertex(0.5, 0.5, 0.5, 1, 0, 0), 16 | // -X Face - cyan 17 | vertex(-0.5, 0.5, 0.5, 0, 1, 1), 18 | vertex(-0.5, 0.5, -0.5, 0, 1, 1), 19 | vertex(-0.5, -0.5, -0.5, 0, 1, 1), 20 | vertex(-0.5, 0.5, 0.5, 0, 1, 1), 21 | vertex(-0.5, -0.5, -0.5, 0, 1, 1), 22 | vertex(-0.5, -0.5, 0.5, 0, 1, 1), 23 | // + Y Face - green 24 | vertex(0.5, 0.5, -0.5, 0, 1, 0), 25 | vertex(-0.5, 0.5, -0.5, 0, 1, 0), 26 | vertex(-0.5, 0.5, 0.5, 0, 1, 0), 27 | vertex(0.5, 0.5, -0.5, 0, 1, 0), 28 | vertex(-0.5, 0.5, 0.5, 0, 1, 0), 29 | vertex(0.5, 0.5, 0.5, 0, 1, 0), 30 | // -Y Face - magenta 31 | vertex(-0.5, -0.5, -0.5, 1, 0, 1), 32 | vertex(0.5, -0.5, -0.5, 1, 0, 1), 33 | vertex(0.5, -0.5, 0.5, 1, 0, 1), 34 | vertex(-0.5, -0.5, -0.5, 1, 0, 1), 35 | vertex(0.5, -0.5, 0.5, 1, 0, 1), 36 | vertex(-0.5, -0.5, 0.5, 1, 0, 1), 37 | // +Z Face - blue 38 | vertex(0.5, 0.5, 0.5, 0, 0, 1), 39 | vertex(-0.5, 0.5, 0.5, 0, 0, 1), 40 | vertex(-0.5, -0.5, 0.5, 0, 0, 1), 41 | vertex(0.5, 0.5, 0.5, 0, 0, 1), 42 | vertex(-0.5, -0.5, 0.5, 0, 0, 1), 43 | vertex(0.5, -0.5, 0.5, 0, 0, 1), 44 | // -Z Face - yellow 45 | vertex(0.5, -0.5, -0.5, 1, 1, 0), 46 | vertex(-0.5, -0.5, -0.5, 1, 1, 0), 47 | vertex(-0.5, 0.5, -0.5, 1, 1, 0), 48 | vertex(0.5, -0.5, -0.5, 1, 1, 0), 49 | vertex(-0.5, 0.5, -0.5, 1, 1, 0), 50 | vertex(0.5, 0.5, -0.5, 1, 1, 0), 51 | } 52 | 53 | vertex :: proc "contextless" (pos1, pos2, pos3, r, g, b: f32) -> Vertex { 54 | return Vertex{position = {pos1, pos2, pos3}, color = {r, g, b}} 55 | } 56 | -------------------------------------------------------------------------------- /render_pipeline.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a rendering (graphics) pipeline. 8 | 9 | A `RenderPipeline` object represents a graphics pipeline and its stages, 10 | bindings, vertex buffers and targets. It can be created with 11 | `DeviceCreateRenderPipeline`. 12 | 13 | Corresponds to [WebGPU 14 | `GPURenderPipeline`](https://gpuweb.github.io/gpuweb/#render-pipeline). 15 | */ 16 | RenderPipeline :: wgpu.RenderPipeline 17 | 18 | /* 19 | Get an object representing the bind group layout at a given index. 20 | 21 | If this pipeline was created with a default layout, then bind groups created 22 | with the returned `BindGroupLayout` can only be used with this pipeline. 23 | 24 | This method will raise a validation error if there is no bind group layout at `index`. 25 | */ 26 | RenderPipelineGetBindGroupLayout :: wgpu.RenderPipelineGetBindGroupLayout 27 | 28 | /* Sets a label for the given `RenderPipeline`. */ 29 | RenderPipelineSetLabel :: #force_inline proc "c" (self: RenderPipeline, label: string) { 30 | wgpu.RenderPipelineSetLabel(self, label) 31 | } 32 | 33 | /* Increase the `RenderPipeline` reference count. */ 34 | RenderPipelineAddRef :: #force_inline proc "c" (self: RenderPipeline) { 35 | wgpu.RenderPipelineAddRef(self) 36 | } 37 | 38 | /* Release the `RenderPipeline` resources, use to decrease the reference count. */ 39 | RenderPipelineRelease :: #force_inline proc "c" (self: RenderPipeline) { 40 | wgpu.RenderPipelineRelease(self) 41 | } 42 | 43 | /* 44 | Safely releases the `RenderPipeline` resources and invalidates the handle. 45 | The procedure checks both the pointer and handle before releasing. 46 | 47 | Note: After calling this, the handle will be set to `nil` and should not be used. 48 | */ 49 | RenderPipelineReleaseSafe :: proc "c" (self: ^RenderPipeline) { 50 | if self != nil && self^ != nil { 51 | wgpu.RenderPipelineRelease(self^) 52 | self^ = nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/rotating_cube/cube.odin: -------------------------------------------------------------------------------- 1 | package rotating_cube 2 | 3 | Vertex :: struct { 4 | position: [4]f32, 5 | color: [4]f32, 6 | tex_coords: [2]f32, 7 | } 8 | 9 | CUBE_VERTEX_DATA :: []Vertex { 10 | // top (0, 0, 1) 11 | Vertex{ {-1, -1, 1, 1}, {0, 0, 1, 1}, {0, 0}} , 12 | Vertex{ { 1, -1, 1, 1}, {1, 0, 1, 1}, {1, 0}} , 13 | Vertex{ { 1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1}} , 14 | Vertex{ {-1, 1, 1, 1}, {0, 1, 1, 1}, {0, 1}} , 15 | // bottom (0, 0, -1) 16 | Vertex{ {-1, 1, -1, 1}, {0, 1, 0, 1}, {1, 0} }, 17 | Vertex{ { 1, 1, -1, 1}, {1, 1, 0, 1}, {0, 0} }, 18 | Vertex{ { 1, -1, -1, 1}, {1, 0, 0, 1}, {0, 1} }, 19 | Vertex{ {-1, -1, -1, 1}, {0, 0, 0, 1}, {1, 1} }, 20 | // right (1, 0, 0) 21 | Vertex{ { 1, -1, -1, 1}, {1, 0, 0, 1}, {0, 0} }, 22 | Vertex{ { 1, 1, -1, 1}, {1, 1, 0, 1}, {1, 0} }, 23 | Vertex{ { 1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1} }, 24 | Vertex{ { 1, -1, 1, 1}, {1, 0, 1, 1}, {0, 1} }, 25 | // left (-1, 0, 0) 26 | Vertex{ {-1, -1, 1, 1}, {0, 0, 1, 1}, {1, 0} }, 27 | Vertex{ {-1, 1, 1, 1}, {0, 1, 1, 1}, {0, 0} }, 28 | Vertex{ {-1, 1, -1, 1}, {0, 1, 0, 1}, {0, 1} }, 29 | Vertex{ {-1, -1, -1, 1}, {0, 0, 0, 1}, {1, 1} }, 30 | // front (0, 1, 0) 31 | Vertex{ { 1, 1, -1, 1}, {1, 1, 0, 1}, {1, 0} }, 32 | Vertex{ {-1, 1, -1, 1}, {0, 1, 0, 1}, {0, 0} }, 33 | Vertex{ {-1, 1, 1, 1}, {0, 1, 1, 1}, {0, 1} }, 34 | Vertex{ { 1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1} }, 35 | // back (0, -1, 0) 36 | Vertex{ { 1, -1, 1, 1}, {1, 0, 1, 1}, {0, 0} }, 37 | Vertex{ {-1, -1, 1, 1}, {0, 0, 1, 1}, {1, 0} }, 38 | Vertex{ {-1, -1, -1, 1}, {0, 0, 0, 1}, {1, 1} }, 39 | Vertex{ { 1, -1, -1, 1}, {1, 0, 0, 1}, {0, 1} }, 40 | } 41 | 42 | CUBE_INDICES_DATA :: []u16 { 43 | 0 , 1, 2, 2, 3, 0, // top 44 | 4 , 5, 6, 6, 7, 4, // bottom 45 | 8 , 9, 10, 10, 11, 8, // right 46 | 12, 13, 14, 14, 15, 12, // left 47 | 16, 17, 18, 18, 19, 16, // front 48 | 20, 21, 22, 22, 23, 20, // back 49 | } 50 | -------------------------------------------------------------------------------- /utils/application/events.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import sa "core:container/small_array" 5 | 6 | EVENTS_MAX_CAPACITY :: #config(VENTS_MAX_CAPACITY, 256) 7 | 8 | Input_Action :: enum { 9 | None, 10 | Pressed, 11 | Released, 12 | } 13 | 14 | Quit_Event :: struct {} 15 | 16 | Resize_Event :: struct { 17 | size: Vec2u, 18 | } 19 | 20 | Key_Event :: struct { 21 | key: Key, 22 | scancode: Scancode, 23 | ctrl: bool, 24 | shift: bool, 25 | alt: bool, 26 | } 27 | 28 | Key_Pressed_Event :: distinct Key_Event 29 | 30 | Key_Released_Event :: distinct Key_Event 31 | 32 | Mouse_Button_Event :: struct { 33 | button: Mouse_Button, 34 | pos: Vec2f, 35 | } 36 | 37 | Mouse_Button_Pressed_Event :: distinct Mouse_Button_Event 38 | 39 | Mouse_Button_Released_Event :: distinct Mouse_Button_Event 40 | 41 | Mouse_Wheel_Event :: distinct Vec2f 42 | 43 | Mouse_Moved_Event :: struct { 44 | pos: Vec2f, 45 | button: Mouse_Button, 46 | action: Input_Action, 47 | } 48 | 49 | Minimized_Event :: struct { 50 | minimized: bool, 51 | } 52 | 53 | Restored_Event :: struct { 54 | restored: bool, 55 | } 56 | 57 | Event :: union { 58 | Quit_Event, 59 | Resize_Event, 60 | Key_Pressed_Event, 61 | Key_Released_Event, 62 | Mouse_Button_Pressed_Event, 63 | Mouse_Button_Released_Event, 64 | Mouse_Wheel_Event, 65 | Mouse_Moved_Event, 66 | Minimized_Event, 67 | Restored_Event, 68 | } 69 | 70 | Events :: struct { 71 | data: sa.Small_Array(EVENTS_MAX_CAPACITY, Event), 72 | } 73 | 74 | events_empty :: proc "contextless" (self: ^Events) -> bool { 75 | return sa.len(self.data) == 0 76 | } 77 | 78 | events_poll :: proc "contextless" (self: ^Events) -> (event: Event, ok: bool) { 79 | return sa.pop_front_safe(&self.data) 80 | } 81 | 82 | events_push :: proc "contextless" (self: ^Events, event: Event) -> bool { 83 | return sa.push_back(&self.data, event) 84 | } 85 | -------------------------------------------------------------------------------- /callbacks.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | MapAsyncStatus :: wgpu.MapAsyncStatus 7 | 8 | Proc :: #type proc "c" () 9 | 10 | BufferMapCallback :: wgpu.BufferMapCallback 11 | 12 | CompilationInfoRequestStatus :: wgpu.CompilationInfoRequestStatus 13 | 14 | CompilationInfoCallback :: wgpu.CompilationInfoCallback 15 | 16 | CreatePipelineAsyncStatus :: wgpu.CreatePipelineAsyncStatus 17 | 18 | CreateComputePipelineAsyncCallback :: wgpu.CreateComputePipelineAsyncCallback 19 | 20 | CreateRenderPipelineAsyncCallback :: wgpu.CreateRenderPipelineAsyncCallback 21 | 22 | DeviceLostReason :: wgpu.DeviceLostReason 23 | 24 | DeviceLostCallback :: wgpu.DeviceLostCallback 25 | 26 | PopErrorScopeStatus :: wgpu.PopErrorScopeStatus 27 | 28 | ErrorType :: wgpu.ErrorType 29 | 30 | PopErrorScopeCallback :: wgpu.PopErrorScopeCallback 31 | 32 | QueueWorkDoneStatus :: wgpu.QueueWorkDoneStatus 33 | 34 | QueueWorkDoneCallback :: wgpu.QueueWorkDoneCallback 35 | 36 | RequestAdapterStatus :: wgpu.RequestAdapterStatus 37 | 38 | RequestAdapterCallback :: wgpu.RequestAdapterCallback 39 | 40 | RequestDeviceStatus :: wgpu.RequestDeviceStatus 41 | 42 | RequestDeviceCallback :: wgpu.RequestDeviceCallback 43 | 44 | UncapturedErrorCallback :: wgpu.UncapturedErrorCallback 45 | 46 | CallbackMode :: wgpu.CallbackMode 47 | 48 | BufferMapCallbackInfo :: wgpu.BufferMapCallbackInfo 49 | 50 | CompilationInfoCallbackInfo :: wgpu.CompilationInfoCallbackInfo 51 | 52 | CreateComputePipelineAsyncCallbackInfo :: wgpu.CreateComputePipelineAsyncCallbackInfo 53 | 54 | CreateRenderPipelineAsyncCallbackInfo :: wgpu.CreateRenderPipelineAsyncCallbackInfo 55 | 56 | DeviceLostCallbackInfo :: wgpu.DeviceLostCallbackInfo 57 | 58 | PopErrorScopeCallbackInfo :: wgpu.PopErrorScopeCallbackInfo 59 | 60 | QueueWorkDoneCallbackInfo :: wgpu.QueueWorkDoneCallbackInfo 61 | 62 | RequestAdapterCallbackInfo :: wgpu.RequestAdapterCallbackInfo 63 | 64 | RequestDeviceCallbackInfo :: wgpu.RequestDeviceCallbackInfo 65 | 66 | UncapturedErrorCallbackInfo :: wgpu.UncapturedErrorCallbackInfo 67 | -------------------------------------------------------------------------------- /compute_pipeline.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Handle to a compute pipeline. 8 | 9 | A `ComputePipeline` object represents a compute pipeline and its single shader 10 | stage. It can be created with `DeviceCreateComputePipeline`. 11 | 12 | Corresponds to [WebGPU 13 | `GPUComputePipeline`](https://gpuweb.github.io/gpuweb/#compute-pipeline). 14 | */ 15 | ComputePipeline :: wgpu.ComputePipeline 16 | 17 | /* 18 | Get an object representing the bind group layout at a given index. 19 | 20 | If this pipeline was created with a default layout, then bind groups created 21 | with the returned `BindGroupLayout` can only be used with this pipeline. 22 | 23 | This method will raise a validation error if there is no bind group layout at `index`. 24 | */ 25 | @(require_results) 26 | ComputePipelineGetBindGroupLayout :: proc "c" ( 27 | self: ComputePipeline, 28 | group_index: u32, 29 | ) -> BindGroupLayout { 30 | return wgpu.ComputePipelineGetBindGroupLayout(self, group_index) 31 | } 32 | 33 | /* Sets a debug label for the given `ComputePipeline`. */ 34 | ComputePipelineSetLabel :: #force_inline proc "c" (self: ComputePipeline, label: string) { 35 | wgpu.ComputePipelineSetLabel(self, label) 36 | } 37 | 38 | /* Increase the `ComputePipeline` reference count. */ 39 | ComputePipelineAddRef :: #force_inline proc "c" (self: ComputePipeline) { 40 | wgpu.ComputePipelineAddRef(self) 41 | } 42 | 43 | /* Release the `ComputePipeline` resources, use to decrease the reference count. */ 44 | ComputePipelineRelease :: #force_inline proc "c" (self: ComputePipeline) { 45 | wgpu.ComputePipelineRelease(self) 46 | } 47 | 48 | /* 49 | Safely releases the `ComputePipeline` resources and invalidates the handle. 50 | The procedure checks both the pointer and handle before releasing. 51 | 52 | Note: After calling this, the handle will be set to `nil` and should not be used. 53 | */ 54 | ComputePipelineReleaseSafe :: proc "c" (self: ^ComputePipeline) { 55 | if self != nil && self^ != nil { 56 | wgpu.ComputePipelineRelease(self^) 57 | self^ = nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /utils/application/window.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import "base:runtime" 5 | 6 | Window :: distinct uintptr 7 | 8 | Window_Styles :: bit_set[Window_Style] 9 | Window_Style :: enum { 10 | Centered, 11 | Resizable, 12 | Borderless, 13 | } 14 | 15 | WINDOW_STYLES_DEFAULT :: Window_Styles{.Centered, .Resizable} 16 | 17 | Window_State :: enum { 18 | Windowed, 19 | Fullscreen, 20 | FullscreenBorderless, 21 | } 22 | 23 | WINDOW_STATE_DEFAULT :: Window_State.Windowed 24 | 25 | Window_Settings :: struct { 26 | styles: Window_Styles, 27 | state: Window_State, 28 | } 29 | 30 | WINDOW_SETTINGS_DEFAULT :: Window_Settings { 31 | styles = WINDOW_STYLES_DEFAULT, 32 | state = WINDOW_STATE_DEFAULT, 33 | } 34 | 35 | MIN_CLIENT_WIDTH :: 1 36 | MIN_CLIENT_HEIGHT :: 1 37 | 38 | Window_Resize_Proc :: #type proc(window: Window, size: Vec2u, userdata: rawptr) 39 | 40 | Window_Resize_Info :: struct { 41 | callback: Window_Resize_Proc, 42 | userdata: rawptr, 43 | } 44 | 45 | Window_Base :: struct { 46 | custom_context: runtime.Context, 47 | allocator: runtime.Allocator, 48 | settings: Window_Settings, 49 | mode: Video_Mode, 50 | title_buf: String_Buffer, 51 | size: Vec2u, 52 | events: Events, 53 | resize_callbacks: [dynamic]Window_Resize_Info, 54 | aspect: f32, 55 | is_minimized: bool, 56 | is_resizing: bool, 57 | is_fullscreen: bool, 58 | } 59 | 60 | window_get_refresh_rate :: proc(window: Window) -> u32 { 61 | impl := _window_get_impl(window) 62 | return impl.mode.refresh_rate 63 | } 64 | 65 | window_get_aspect :: proc(window: Window) -> f32 { 66 | impl := _window_get_impl(window) 67 | return impl.aspect 68 | } 69 | 70 | window_add_resize_callback :: proc(window: Window, cb: Window_Resize_Info) { 71 | impl := _window_get_impl(window) 72 | append(&impl.resize_callbacks, cb) 73 | } 74 | 75 | // ----------------------------------------------------------------------------- 76 | // @(private) 77 | // ----------------------------------------------------------------------------- 78 | 79 | @(private, require_results) 80 | _window_get_impl :: #force_inline proc "contextless" (window: Window) -> ^Window_Impl { 81 | return cast(^Window_Impl)window 82 | } 83 | -------------------------------------------------------------------------------- /utils/application/run_native.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package application 3 | 4 | run :: proc(app := app_context) { 5 | assert(!app.prepared, "Application already initialized") 6 | 7 | // Set up window callbacks 8 | _window_setup_callbacks(app.window) 9 | 10 | // Initialize the user application 11 | if app.callbacks.init != nil { 12 | if res := app.callbacks.init(app); !res { 13 | return 14 | } 15 | } 16 | 17 | defer { 18 | if app.callbacks.quit != nil { 19 | app.callbacks.quit(app) 20 | } 21 | destroy(app) 22 | } 23 | 24 | margin_ms := 0.5 // Wake up early for busy wait accuracy 25 | target_frame_time_ms := 1000.0 / f64(window_get_refresh_rate(app.window)) 26 | timer_init(&app.timer, margin_ms, target_frame_time_ms) 27 | 28 | app.prepared = true 29 | app.running = true 30 | 31 | MAIN_LOOP: for app.running { 32 | // Process events 33 | // Events are handled directly via callbacks - no need to poll queue 34 | window_process_events(app.window) 35 | 36 | timer_begin_frame(&app.timer) 37 | when ODIN_DEBUG { 38 | _update_window_title_with_fps(app) 39 | } 40 | 41 | keyboard_update() 42 | mouse_update() 43 | 44 | // Application iteration 45 | if app.callbacks.step != nil { 46 | if !app.callbacks.step(app, f32(timer_get_delta(&app.timer))) { 47 | app.running = false 48 | break MAIN_LOOP 49 | } 50 | } 51 | 52 | timer_end_frame(&app.timer) 53 | gpu_pace_frame(app.gpu, &app.timer) 54 | } 55 | } 56 | 57 | // ----------------------------------------------------------------------------- 58 | // @(private) 59 | // ----------------------------------------------------------------------------- 60 | 61 | @(private, disabled = !ODIN_DEBUG) 62 | _update_window_title_with_fps :: proc(app := app_context) { 63 | if !timer_get_fps_update(&app.timer) { 64 | return 65 | } 66 | 67 | window_impl := _window_get_impl(app.window) 68 | title := string_buffer_get_string(&window_impl.title_buf) 69 | 70 | title_buf: String_Buffer 71 | string_buffer_init(&title_buf, title) 72 | string_buffer_append(&title_buf, " - FPS = ") 73 | 74 | fps_buf: [4]u8 75 | fps := timer_get_fps(&app.timer) 76 | string_buffer_append_f64(&title_buf, fps_buf[:], fps, decimals = 1) 77 | 78 | // This call does not change window.title_buf 79 | window_set_title_cstring(app.window, string_buffer_get_cstring(&title_buf)) 80 | } 81 | -------------------------------------------------------------------------------- /color_blend.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | Describes a blend component of a `BlendState`. 8 | 9 | Corresponds to [WebGPU `GPUBlendComponent`]( 10 | https://gpuweb.github.io/gpuweb/#dictdef-gpublendcomponent). 11 | */ 12 | BlendComponent :: wgpu.BlendComponent 13 | 14 | /* Standard blending state that blends source and destination based on source alpha. */ 15 | BLEND_COMPONENT_NORMAL :: BlendComponent { 16 | operation = .Add, 17 | srcFactor = .SrcAlpha, 18 | dstFactor = .OneMinusSrcAlpha, 19 | } 20 | 21 | // Default blending state that replaces destination with the source. 22 | BLEND_COMPONENT_REPLACE :: BlendComponent { 23 | operation = .Add, 24 | srcFactor = .One, 25 | dstFactor = .Zero, 26 | } 27 | 28 | // Blend state of (1 * src) + ((1 - src_alpha) * dst) 29 | BLEND_COMPONENT_OVER :: BlendComponent { 30 | operation = .Add, 31 | srcFactor = .One, 32 | dstFactor = .OneMinusSrcAlpha, 33 | } 34 | 35 | BLEND_COMPONENT_DEFAULT :: BLEND_COMPONENT_REPLACE 36 | 37 | /* 38 | Returns `true` if the state relies on the constant color, which is set 39 | independently on a render command encoder. 40 | */ 41 | BlendComponentUsesConstant :: proc "c" (self: BlendComponent) -> bool { 42 | return( 43 | self.srcFactor == .Constant || 44 | self.srcFactor == .OneMinusConstant || 45 | self.dstFactor == .Constant || 46 | self.dstFactor == .OneMinusConstant \ 47 | ) 48 | } 49 | 50 | /* 51 | Describe the blend state of a render pipeline, 52 | within `Color_Target_State`. 53 | 54 | Corresponds to [WebGPU `GPUBlendState`]( 55 | https://gpuweb.github.io/gpuweb/#dictdef-gpublendstate). 56 | */ 57 | BlendState :: wgpu.BlendState 58 | 59 | /* Uses alpha blending for both color and alpha channels. */ 60 | @(rodata) 61 | BLEND_STATE_NORMAL := BlendState { 62 | color = BLEND_COMPONENT_NORMAL, 63 | alpha = BLEND_COMPONENT_NORMAL, 64 | } 65 | 66 | /* Does no color blending, just overwrites the output with the contents of the shader. */ 67 | @(rodata) 68 | BLEND_STATE_REPLACE := BlendState { 69 | color = BLEND_COMPONENT_REPLACE, 70 | alpha = BLEND_COMPONENT_REPLACE, 71 | } 72 | 73 | /* Does standard alpha blending with non-premultiplied alpha. */ 74 | @(rodata) 75 | BLEND_STATE_ALPHA_BLENDING := BlendState { 76 | color = BLEND_COMPONENT_NORMAL, 77 | alpha = BLEND_COMPONENT_OVER, 78 | } 79 | 80 | /* Does standard alpha blending with premultiplied alpha. */ 81 | @(rodata) 82 | BLEND_STATE_PREMULTIPLIED_ALPHA_BLENDING := BlendState { 83 | color = BLEND_COMPONENT_OVER, 84 | alpha = BLEND_COMPONENT_OVER, 85 | } 86 | -------------------------------------------------------------------------------- /examples/common/model.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package examples_common 3 | 4 | // Core 5 | import "base:runtime" 6 | import la "core:math/linalg" 7 | 8 | // Local packages 9 | import wgpu "../../" 10 | import app "../../utils/application" 11 | 12 | Material :: struct { 13 | allocator: runtime.Allocator, 14 | name: string, 15 | diffuse_texture: app.Texture, 16 | bind_group: wgpu.BindGroup, 17 | } 18 | 19 | Mesh :: struct { 20 | allocator: runtime.Allocator, 21 | name: string, 22 | vertex_buffer: wgpu.Buffer, 23 | index_buffer: wgpu.Buffer, 24 | num_elements: u32, 25 | material_id: uint, 26 | } 27 | 28 | Model :: struct { 29 | allocator: runtime.Allocator, 30 | meshes: []Mesh, 31 | materials: []Material, 32 | } 33 | 34 | Model_Vertex :: struct { 35 | vertices: la.Vector3f32, 36 | texture_coords: la.Vector2f32, 37 | normals: la.Vector3f32, 38 | } 39 | 40 | MODEL_VERTEX_LAYOUT :: wgpu.VertexBufferLayout { 41 | arrayStride = size_of(Model_Vertex), 42 | stepMode = .Vertex, 43 | attributes = { 44 | {offset = 0, shaderLocation = 0, format = .Float32x3}, 45 | { 46 | offset = u64(offset_of(Model_Vertex, texture_coords)), 47 | shaderLocation = 1, 48 | format = .Float32x2, 49 | }, 50 | {offset = u64(offset_of(Model_Vertex, normals)), shaderLocation = 2, format = .Float32x3}, 51 | }, 52 | } 53 | 54 | mesh_draw :: proc( 55 | rpass: wgpu.RenderPass, 56 | mesh: Mesh, 57 | material: Material, 58 | camera_bind_group: wgpu.BindGroup, 59 | ) { 60 | #force_inline mesh_draw_instanced(rpass, mesh, material, {0, 1}, camera_bind_group) 61 | } 62 | 63 | mesh_draw_instanced :: proc( 64 | rpass: wgpu.RenderPass, 65 | mesh: Mesh, 66 | material: Material, 67 | instances: wgpu.Range(u32), 68 | camera_bind_group: wgpu.BindGroup, 69 | ) { 70 | wgpu.RenderPassSetVertexBuffer(rpass, 0, {buffer = mesh.vertex_buffer}) 71 | wgpu.RenderPassSetIndexBuffer(rpass, {buffer = mesh.index_buffer}, .Uint32) 72 | wgpu.RenderPassSetBindGroup(rpass, 0, material.bind_group) 73 | wgpu.RenderPassSetBindGroup(rpass, 1, camera_bind_group) 74 | wgpu.RenderPassDrawIndexed(rpass, {0, mesh.num_elements}, 0, instances) 75 | } 76 | 77 | model_draw :: proc(rpass: wgpu.RenderPass, model: ^Model, camera_bind_group: wgpu.BindGroup) { 78 | #force_inline model_draw_instanced(rpass, model, {0, 1}, camera_bind_group) 79 | } 80 | 81 | model_draw_instanced :: proc( 82 | rpass: wgpu.RenderPass, 83 | model: ^Model, 84 | instances: wgpu.Range(u32), 85 | camera_bind_group: wgpu.BindGroup, 86 | ) { 87 | for &m in model.meshes { 88 | material := model.materials[m.material_id] 89 | mesh_draw_instanced(rpass, m, material, instances, camera_bind_group) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/cameras/cameera.odin: -------------------------------------------------------------------------------- 1 | package cameras_example 2 | 3 | // Core 4 | import la "core:math/linalg" 5 | 6 | // User input state. 7 | Input :: struct { 8 | analog: Analog_Input, // Analog input (e.g mouse, touchscreen) 9 | digital: Digital_Input, // Digital input (e.g keyboard state) 10 | } 11 | 12 | // Represents continuous input values like mouse movement 13 | Analog_Input :: struct { 14 | x: f32, // Horizontal analog input 15 | y: f32, // Vertical analog input 16 | zoom: f32, // Zoom input value 17 | touching: bool, // Whether touch/click is active 18 | } 19 | 20 | // Represents discrete on/off input states like keyboard keys 21 | Digital_Input :: struct { 22 | forward: bool, // Forward movement 23 | backward: bool, // Backward movement 24 | left: bool, // Left movement 25 | right: bool, // Right movement 26 | up: bool, // Upward movement 27 | down: bool, // Downward movement 28 | } 29 | 30 | // Camera interface common properties 31 | Camera :: struct { 32 | mat: la.Matrix4f32, // Camera matrix (inverse of view matrix) 33 | view: la.Matrix4f32, // View matrix 34 | right: la.Vector3f32, // Right vector (first column) 35 | up: la.Vector3f32, // Up vector (second column) 36 | back: la.Vector3f32, // Back vector (third column) 37 | position: la.Vector3f32, // Position vector (fourth column) 38 | } 39 | 40 | // Helper procedures to update the matrix when vectors change 41 | update_matrix_from_vectors :: proc "contextless" (camera: ^Camera) { 42 | // First column (right vector) 43 | camera.mat[0, 0] = camera.right.x 44 | camera.mat[1, 0] = camera.right.y 45 | camera.mat[2, 0] = camera.right.z 46 | camera.mat[3, 0] = 0 47 | 48 | // Second column (up vector) 49 | camera.mat[0, 1] = camera.up.x 50 | camera.mat[1, 1] = camera.up.y 51 | camera.mat[2, 1] = camera.up.z 52 | camera.mat[3, 1] = 0 53 | 54 | // Third column (back vector) 55 | camera.mat[0, 2] = camera.back.x 56 | camera.mat[1, 2] = camera.back.y 57 | camera.mat[2, 2] = camera.back.z 58 | camera.mat[3, 2] = 0 59 | 60 | // Fourth column (position vector) 61 | camera.mat[0, 3] = camera.position.x 62 | camera.mat[1, 3] = camera.position.y 63 | camera.mat[2, 3] = camera.position.z 64 | camera.mat[3, 3] = 1 65 | 66 | camera.view = la.matrix4_inverse_f32(camera.mat) 67 | } 68 | 69 | scale :: #force_inline proc "contextless" (v: la.Vector3f32, scale: f32) -> la.Vector3f32 { 70 | return v * scale 71 | } 72 | 73 | // Returns `Vector3f32` rotated `angle` radians around `axis`. 74 | rotate :: proc "contextless" (v: la.Vector3f32, axis: la.Vector3f32, angle: f32) -> la.Vector3f32 { 75 | rotation := la.matrix4_rotate(angle, axis) 76 | return (rotation * la.Vector4f32{v.x, v.y, v.z, 0}).xyz 77 | } 78 | 79 | // Boolean to f32 helper. 80 | b2f :: #force_inline proc "contextless" (b: bool) -> f32 { 81 | return b ? 1.0 : 0.0 82 | } 83 | 84 | camera_update :: proc { 85 | arcball_camera_update, 86 | wasd_camera_update, 87 | } 88 | -------------------------------------------------------------------------------- /examples/image_blur/blur.wgsl: -------------------------------------------------------------------------------- 1 | struct Params { 2 | filterDim : i32, 3 | blockDim : u32, 4 | } 5 | 6 | @group(0) @binding(0) var samp : sampler; 7 | @group(0) @binding(1) var params : Params; 8 | @group(1) @binding(1) var inputTex : texture_2d; 9 | @group(1) @binding(2) var outputTex : texture_storage_2d; 10 | 11 | struct Flip { 12 | value : u32, 13 | } 14 | @group(1) @binding(3) var flip : Flip; 15 | 16 | // This shader blurs the input texture in one direction, depending on whether 17 | // |flip.value| is 0 or 1. 18 | // It does so by running (128 / 4) threads per workgroup to load 128 19 | // texels into 4 rows of shared memory. Each thread loads a 20 | // 4 x 4 block of texels to take advantage of the texture sampling 21 | // hardware. 22 | // Then, each thread computes the blur result by averaging the adjacent texel values 23 | // in shared memory. 24 | // Because we're operating on a subset of the texture, we cannot compute all of the 25 | // results since not all of the neighbors are available in shared memory. 26 | // Specifically, with 128 x 128 tiles, we can only compute and write out 27 | // square blocks of size 128 - (filterSize - 1). We compute the number of blocks 28 | // needed in the application and dispatch that amount. 29 | 30 | var tile : array, 4>; 31 | 32 | @compute @workgroup_size(32, 1, 1) 33 | fn main( 34 | @builtin(workgroup_id) WorkGroupID : vec3u, 35 | @builtin(local_invocation_id) LocalInvocationID : vec3u 36 | ) { 37 | let filterOffset = (params.filterDim - 1) / 2; 38 | let dims = vec2i(textureDimensions(inputTex, 0)); 39 | let baseIndex = vec2i(WorkGroupID.xy * vec2(params.blockDim, 4) + 40 | LocalInvocationID.xy * vec2(4, 1)) 41 | - vec2(filterOffset, 0); 42 | 43 | for (var r = 0; r < 4; r++) { 44 | for (var c = 0; c < 4; c++) { 45 | var loadIndex = baseIndex + vec2(c, r); 46 | if (flip.value != 0u) { 47 | loadIndex = loadIndex.yx; 48 | } 49 | 50 | tile[r][4 * LocalInvocationID.x + u32(c)] = textureSampleLevel( 51 | inputTex, 52 | samp, 53 | (vec2f(loadIndex) + vec2f(0.25, 0.25)) / vec2f(dims), 54 | 0.0 55 | ).rgb; 56 | } 57 | } 58 | 59 | workgroupBarrier(); 60 | 61 | for (var r = 0; r < 4; r++) { 62 | for (var c = 0; c < 4; c++) { 63 | var writeIndex = baseIndex + vec2(c, r); 64 | if (flip.value != 0) { 65 | writeIndex = writeIndex.yx; 66 | } 67 | 68 | let center = i32(4 * LocalInvocationID.x) + c; 69 | if (center >= filterOffset && 70 | center < 128 - filterOffset && 71 | all(writeIndex < dims)) { 72 | var acc = vec3(0.0, 0.0, 0.0); 73 | for (var f = 0; f < params.filterDim; f++) { 74 | var i = center + f - filterOffset; 75 | acc = acc + (1.0 / f32(params.filterDim)) * tile[r][i]; 76 | } 77 | textureStore(outputTex, writeIndex, vec4(acc, 1.0)); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /compute_pass.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Vendor 4 | import "vendor:wgpu" 5 | 6 | /* 7 | In-progress recording of a compute pass. 8 | 9 | It can be created with `CommandEncoderBeginComputePass`. 10 | 11 | Corresponds to [WebGPU `GPUComputePassEncoder`]( 12 | https://gpuweb.github.io/gpuweb/#compute-pass-encoder). 13 | */ 14 | ComputePass :: wgpu.ComputePassEncoder 15 | 16 | /* 17 | Sets the active bind group for a given bind group index. The bind group layout 18 | in the active pipeline when the `ComputePassDispatchWorkgroups` function is 19 | called must match the layout of this bind group. 20 | 21 | If the bind group have dynamic offsets, provide them in the binding order. These 22 | offsets have to be aligned to limits `MinUniformBufferOffsetAlignment` or limits 23 | `MinStorageBufferOffsetAlignment` appropriately. 24 | */ 25 | ComputePassSetBindGroup :: wgpu.ComputePassEncoderSetBindGroup 26 | 27 | /* Sets the active compute pipeline. */ 28 | ComputePassSetPipeline :: wgpu.ComputePassEncoderSetPipeline 29 | 30 | /* Inserts debug marker. */ 31 | ComputePassInsertDebugMarker :: wgpu.ComputePassEncoderInsertDebugMarker 32 | 33 | /* Start record commands and group it into debug marker group. */ 34 | ComputePassPushDebugGroup :: wgpu.ComputePassEncoderPushDebugGroup 35 | 36 | /* Stops command recording and creates debug group. */ 37 | ComputePassPopDebugGroup :: wgpu.ComputePassEncoderPopDebugGroup 38 | 39 | /* 40 | Dispatches compute work operations. 41 | 42 | `x`, `y` and `z` denote the number of work groups to dispatch in each dimension. 43 | */ 44 | ComputePassDispatchWorkgroups :: wgpu.ComputePassEncoderDispatchWorkgroups 45 | 46 | /* 47 | Dispatches compute work operations, based on the contents of the `indirect_buffer`. 48 | 49 | The structure expected in `indirect_buffer` must conform to `Dispatch_Indirect`. 50 | */ 51 | ComputePassDispatchWorkgroupsIndirect :: wgpu.ComputePassEncoderDispatchWorkgroupsIndirect 52 | 53 | /* Record the end of the compute pass. */ 54 | ComputePassEnd :: wgpu.ComputePassEncoderEnd 55 | 56 | /* Sets a debug label for the given `ComputePass`. */ 57 | ComputePassSetLabel :: #force_inline proc "c" (self: ComputePass, label: string) { 58 | wgpu.ComputePassEncoderSetLabel(self, label) 59 | } 60 | 61 | /* Increase the `ComputePass` reference count. */ 62 | ComputePassAddRef :: #force_inline proc "c" (self: ComputePass) { 63 | wgpu.ComputePassEncoderAddRef(self) 64 | } 65 | 66 | /* Release the `ComputePass` resources, use to decrease the reference count. */ 67 | ComputePassRelease :: #force_inline proc "c" (self: ComputePass) { 68 | wgpu.ComputePassEncoderRelease(self) 69 | } 70 | 71 | /* 72 | Safely releases the `ComputePass` resources and invalidates the handle. 73 | The procedure checks both the pointer and handle before releasing. 74 | 75 | Note: After calling this, the handle will be set to `nil` and should not be used. 76 | */ 77 | ComputePassReleaseSafe :: proc "c" (self: ^ComputePass) { 78 | if self != nil && self^ != nil { 79 | wgpu.ComputePassEncoderRelease(self^) 80 | self^ = nil 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/clear_screen/clear_screen.odin: -------------------------------------------------------------------------------- 1 | package clear_screen 2 | 3 | // Core 4 | import "core:log" 5 | import "core:math" 6 | 7 | // Local packages 8 | import wgpu "../.." 9 | import app "../../utils/application" 10 | 11 | CLIENT_WIDTH :: 640 12 | CLIENT_HEIGHT :: 480 13 | EXAMPLE_TITLE :: "Clear Screen" 14 | VIDEO_MODE_DEFAULT :: app.Video_Mode { 15 | width = CLIENT_WIDTH, 16 | height = CLIENT_HEIGHT, 17 | } 18 | 19 | Application :: struct { 20 | using _app: app.Application, 21 | clear_value: app.Color, 22 | rpass: struct { 23 | colors: [1]wgpu.RenderPassColorAttachment, 24 | descriptor: wgpu.RenderPassDescriptor, 25 | }, 26 | } 27 | 28 | init :: proc(self: ^Application) -> (ok: bool) { 29 | self.clear_value = app.Color_Black 30 | 31 | self.rpass.colors[0] = { 32 | view = nil, /* Assigned later */ 33 | ops = { 34 | load = .Clear, 35 | store = .Store, 36 | clearValue = self.clear_value, 37 | }, 38 | } 39 | 40 | self.rpass.descriptor = { 41 | label = "Render pass descriptor", 42 | colorAttachments = self.rpass.colors[:], 43 | } 44 | 45 | return true 46 | } 47 | 48 | update :: proc(self: ^Application, dt: f32) -> (ok: bool) { 49 | current_time := app.get_time() 50 | color := [4]f64 { 51 | math.sin(f64(current_time)) * 0.5 + 0.5, 52 | math.cos(f64(current_time)) * 0.5 + 0.5, 53 | 0.0, 54 | 1.0, 55 | } 56 | self.rpass.colors[0].ops.clearValue = color 57 | return true 58 | } 59 | 60 | step :: proc(self: ^Application, dt: f32) -> (ok: bool) { 61 | update(self, dt) or_return 62 | 63 | frame := app.gpu_get_current_frame(self.gpu) 64 | if frame.skip { return } 65 | defer app.gpu_release_current_frame(&frame) 66 | 67 | encoder := wgpu.DeviceCreateCommandEncoder(self.gpu.device) 68 | defer wgpu.CommandEncoderRelease(encoder) 69 | 70 | self.rpass.colors[0].view = frame.view 71 | rpass := wgpu.CommandEncoderBeginRenderPass(encoder, self.rpass.descriptor) 72 | wgpu.RenderPassEnd(rpass) 73 | wgpu.RenderPassRelease(rpass) 74 | 75 | cmdbuf := wgpu.CommandEncoderFinish(encoder) 76 | defer wgpu.CommandBufferRelease(cmdbuf) 77 | 78 | wgpu.QueueSubmit(self.gpu.queue, { cmdbuf }) 79 | wgpu.SurfacePresent(self.gpu.surface) 80 | 81 | return true 82 | } 83 | 84 | event :: proc(self: ^Application, event: app.Event) -> (ok: bool) { 85 | #partial switch &ev in event { 86 | case app.Quit_Event: 87 | log.info("Exiting...") 88 | return 89 | } 90 | return true 91 | } 92 | 93 | quit :: proc(app: ^Application) { 94 | } 95 | 96 | main :: proc() { 97 | when ODIN_DEBUG { 98 | context.logger = log.create_console_logger(opt = {.Level, .Terminal_Color}) 99 | defer log.destroy_console_logger(context.logger) 100 | } 101 | 102 | callbacks := app.Application_Callbacks{ 103 | init = app.App_Init_Callback(init), 104 | step = app.App_Step_Callback(step), 105 | event = app.App_Event_Callback(event), 106 | quit = app.App_Quit_Callback(quit), 107 | } 108 | 109 | app.init(Application, VIDEO_MODE_DEFAULT, EXAMPLE_TITLE, callbacks) 110 | } 111 | -------------------------------------------------------------------------------- /utils/application/mouse.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | Mouse_Button :: enum i32 { 4 | Unknown, 5 | Left, 6 | Middle, 7 | Right, 8 | Four, 9 | Five, 10 | } 11 | 12 | /* Total number of mouse buttons available. */ 13 | MOUSE_BUTTON_COUNT :: len(Mouse_Button) 14 | 15 | /* State of the mouse, including its position, button states, and other relevant information. */ 16 | Mouse_State :: struct { 17 | current: [MOUSE_BUTTON_COUNT]bool, 18 | previous: [MOUSE_BUTTON_COUNT]bool, 19 | last_button_pressed: Mouse_Button, 20 | position: [2]f64, /* x, y coordinates */ 21 | previous_position: [2]f64, 22 | previous_scroll: [2]f64, /* x, y scroll offsets */ 23 | scroll: [2]f64, /* x, y scroll offsets */ 24 | button_repeat: bool, 25 | } 26 | 27 | /* Checks if a specific mouse button has been just pressed. */ 28 | mouse_button_is_pressed :: #force_inline proc "contextless" ( 29 | button: Mouse_Button, 30 | app := app_context, 31 | ) -> bool #no_bounds_check { 32 | return app.mouse.current[button] && !app.mouse.previous[button] 33 | } 34 | 35 | /* Checks if a specific mouse button is currently pressed down. */ 36 | mouse_button_is_down :: #force_inline proc "contextless" ( 37 | button: Mouse_Button, 38 | app := app_context, 39 | ) -> bool #no_bounds_check { 40 | return app.mouse.current[button] 41 | } 42 | 43 | /* Checks if a mouse button has been just released. */ 44 | mouse_button_is_released :: #force_inline proc "contextless" ( 45 | app := app_context, 46 | button: Mouse_Button, 47 | ) -> bool #no_bounds_check { 48 | return !app.mouse.current[button] && app.mouse.previous[button] 49 | } 50 | 51 | /* Checks if a specific mouse button is currently not pressed. */ 52 | mouse_button_is_up :: #force_inline proc "contextless" ( 53 | app := app_context, 54 | button: Mouse_Button, 55 | ) -> bool #no_bounds_check { 56 | return !app.mouse.current[button] 57 | } 58 | 59 | /* Returns the currently pressed mouse button. */ 60 | mouse_button_get_pressed :: #force_inline proc "contextless" (app := app_context) -> Mouse_Button { 61 | return app.mouse.last_button_pressed 62 | } 63 | 64 | /* Returns the current position of the mouse cursor. */ 65 | mouse_get_position :: #force_inline proc "contextless" (app := app_context) -> [2]f64 { 66 | return app.mouse.position 67 | } 68 | 69 | /* Returns the previous position of the mouse cursor. */ 70 | mouse_get_previous_position :: #force_inline proc "contextless" (app := app_context) -> [2]f64 { 71 | return app.mouse.previous_position 72 | } 73 | 74 | /* Retrieves the current mouse movement. */ 75 | mouse_get_movement :: #force_inline proc "contextless" ( 76 | app := app_context, 77 | ) -> [2]f64 #no_bounds_check { 78 | return { 79 | app.mouse.position[0] - app.mouse.previous_position[0], 80 | app.mouse.position[1] - app.mouse.previous_position[1], 81 | } 82 | } 83 | 84 | /* 85 | Retrieves the current scroll values of the mouse. 86 | 87 | Returns: 88 | - A 2-element array of f64 where the first element is the horizontal scroll value and the second 89 | element is the vertical scroll value. 90 | */ 91 | mouse_get_scroll :: #force_inline proc "contextless" (app := app_context) -> [2]f64 { 92 | return app.mouse.previous_scroll 93 | } 94 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial2_surface_challenge/tutorial2_surface_challenge.odin: -------------------------------------------------------------------------------- 1 | package tutorial2_surface_challenge 2 | 3 | // Core 4 | import "core:log" 5 | 6 | // Local packages 7 | import wgpu "../../../../" 8 | import app "../../../../utils/application" 9 | 10 | CLIENT_WIDTH :: 640 11 | CLIENT_HEIGHT :: 480 12 | EXAMPLE_TITLE :: "Tutorial 2 - Surface Challenge" 13 | VIDEO_MODE_DEFAULT :: app.Video_Mode { 14 | width = CLIENT_WIDTH, 15 | height = CLIENT_HEIGHT, 16 | } 17 | 18 | Application :: struct { 19 | using _app: app.Application, /* #subtype */ 20 | clear_value: app.Color, 21 | rpass: struct { 22 | colors: [1]wgpu.RenderPassColorAttachment, 23 | descriptor: wgpu.RenderPassDescriptor, 24 | }, 25 | } 26 | 27 | init :: proc(self: ^Application) -> (ok: bool) { 28 | self.clear_value = app.Color_Royal_Blue 29 | 30 | self.rpass.colors[0] = { 31 | view = nil, /* Assigned later */ 32 | ops = {.Clear, .Store, self.clear_value}, 33 | } 34 | 35 | self.rpass.descriptor = { 36 | label = "Render pass descriptor", 37 | colorAttachments = self.rpass.colors[:], 38 | } 39 | 40 | return true 41 | } 42 | 43 | step :: proc(self: ^Application, dt: f32) -> (ok: bool) { 44 | frame := app.gpu_get_current_frame(self.gpu) 45 | if frame.skip { return } 46 | defer app.gpu_release_current_frame(&frame) 47 | 48 | encoder := wgpu.DeviceCreateCommandEncoder(self.gpu.device) 49 | defer wgpu.Release(encoder) 50 | 51 | self.rpass.colors[0].view = frame.view 52 | self.rpass.colors[0].ops.clearValue = self.clear_value 53 | rpass := wgpu.CommandEncoderBeginRenderPass(encoder, self.rpass.descriptor) 54 | defer wgpu.Release(rpass) 55 | wgpu.RenderPassEnd(rpass) 56 | 57 | cmdbuf := wgpu.CommandEncoderFinish(encoder) 58 | defer wgpu.Release(cmdbuf) 59 | 60 | wgpu.QueueSubmit(self.gpu.queue, { cmdbuf }) 61 | wgpu.SurfacePresent(self.gpu.surface) 62 | 63 | return true 64 | } 65 | 66 | event :: proc(self: ^Application, event: app.Event) -> (ok: bool) { 67 | #partial switch &ev in event { 68 | case app.Quit_Event: 69 | log.info("Exiting...") 70 | return 71 | 72 | case app.Mouse_Moved_Event: 73 | mouse_moved_event(self, ev) 74 | } 75 | return true 76 | } 77 | 78 | quit :: proc(self: ^Application) { 79 | } 80 | 81 | color_from_mouse_position :: proc(x, y: f32, w, h: u32) -> (color: app.Color) { 82 | color.r = cast(f64)x / cast(f64)w 83 | color.g = cast(f64)y / cast(f64)h 84 | color.b = 1.0 85 | color.a = 1.0 86 | return 87 | } 88 | 89 | mouse_moved_event :: proc(self: ^Application, event: app.Mouse_Moved_Event) { 90 | self.clear_value = color_from_mouse_position( 91 | event.pos.x, 92 | event.pos.y, 93 | self.gpu.config.width, 94 | self.gpu.config.height, 95 | ) 96 | } 97 | 98 | main :: proc() { 99 | when ODIN_DEBUG { 100 | context.logger = log.create_console_logger(opt = {.Level, .Terminal_Color}) 101 | defer log.destroy_console_logger(context.logger) 102 | } 103 | 104 | callbacks := app.Application_Callbacks{ 105 | init = app.App_Init_Callback(init), 106 | step = app.App_Step_Callback(step), 107 | event = app.App_Event_Callback(event), 108 | quit = app.App_Quit_Callback(quit), 109 | } 110 | 111 | app.init(Application, VIDEO_MODE_DEFAULT, EXAMPLE_TITLE, callbacks) 112 | } 113 | -------------------------------------------------------------------------------- /shader_module.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Core 4 | import "base:runtime" 5 | import "core:os" 6 | import "core:slice" 7 | 8 | // Vendor 9 | import "vendor:wgpu" 10 | 11 | /* 12 | Handle to a compiled shader module. 13 | 14 | A `ShaderModule` represents a compiled shader module on the GPU. It can be 15 | created by passing source code to `DeviceCreateShaderModule` or valid SPIR-V 16 | binary to `DeviceCreateShaderModuleSpirV`. Shader modules are used to define 17 | programmable stages of a pipeline. 18 | 19 | Corresponds to [WebGPU 20 | `GPUShaderModule`](https://gpuweb.github.io/gpuweb/#shader-module). 21 | */ 22 | ShaderModule :: wgpu.ShaderModule 23 | 24 | /* 25 | Load a WGSL shader file and return a compiled shader module. 26 | */ 27 | DeviceLoadWgslShaderModule :: proc( 28 | self: Device, 29 | path: string, 30 | label: string = "", 31 | loc := #caller_location, 32 | ) -> ( 33 | shaderModule: ShaderModule, 34 | ) { 35 | runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() 36 | source, source_ok := os.read_entire_file(path, context.temp_allocator, loc) 37 | assert(source_ok, "Failed to read shader path", loc) 38 | 39 | descriptor := ShaderModuleDescriptor { 40 | label = label if label != "" else path, 41 | source = string(source), 42 | } 43 | 44 | shaderModule = DeviceCreateShaderModule(self, descriptor) 45 | 46 | return 47 | } 48 | 49 | /* Load a spirv shader file and return a compiled shader module. */ 50 | DeviceLoadSpirVShaderModule :: proc( 51 | self: Device, 52 | path: string, 53 | label: string = "", 54 | loc := #caller_location, 55 | ) -> ( 56 | shaderModule: ShaderModule, 57 | ) { 58 | runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() 59 | source, source_ok := os.read_entire_file(path, context.temp_allocator, loc) 60 | assert(source_ok, "Failed to read shader path", loc) 61 | 62 | descriptor := ShaderModuleDescriptor { 63 | label = label if label != "" else path, 64 | source = slice.reinterpret([]u32, source), 65 | } 66 | 67 | return DeviceCreateShaderModule(self, descriptor) 68 | } 69 | 70 | CompilationMessage :: wgpu.CompilationMessage 71 | 72 | /* 73 | Compilation information for a shader module. 74 | 75 | Corresponds to [WebGPU 76 | `GPUCompilationInfo`](https://gpuweb.github.io/gpuweb/#gpucompilationinfo). The 77 | source locations use bytes, and index a UTF-8 encoded string. 78 | */ 79 | CompilationInfo :: struct { 80 | messages: []CompilationMessage, 81 | } 82 | 83 | /* Get the compilation info for the shader module. */ 84 | ShaderModuleGetCompilationInfo :: proc(self: ShaderModule) -> CompilationInfo { 85 | unimplemented() 86 | } 87 | 88 | /* Sets a debug label for the given `ShaderModule`. */ 89 | ShaderModuleSetLabel :: #force_inline proc "c" (self: ShaderModule, label: string) { 90 | wgpu.ShaderModuleSetLabel(self, label) 91 | } 92 | 93 | /* Increase the `ShaderModule` reference count. */ 94 | ShaderModuleAddRef :: #force_inline proc "c" (self: ShaderModule) { 95 | wgpu.ShaderModuleAddRef(self) 96 | } 97 | 98 | /* Release the `ShaderModule` resources, use to decrease the reference count. */ 99 | ShaderModuleRelease :: #force_inline proc "c" (self: ShaderModule) { 100 | wgpu.ShaderModuleRelease(self) 101 | } 102 | 103 | /* 104 | Safely releases the `ShaderModule` resources and invalidates the handle. The 105 | procedure checks both the pointer and handle before releasing. 106 | 107 | Note: After calling this, the handle will be set to `nil` and should not be used. 108 | */ 109 | ShaderModuleReleaseSafe :: proc "c" (self: ^ShaderModule) { 110 | if self != nil && self^ != nil { 111 | wgpu.ShaderModuleRelease(self^) 112 | self^ = nil 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /to_bytes.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | // Core 4 | import "core:bytes" 5 | import "core:log" 6 | import "core:reflect" 7 | import "core:slice" 8 | 9 | FromBytes :: slice.reinterpret 10 | 11 | when ODIN_DEBUG { 12 | @(private = "file") 13 | _can_be_bytes :: proc(T: typeid) -> bool { 14 | id := reflect.typeid_core(T) 15 | kind := reflect.type_kind(id) 16 | 17 | for kind == .Array || kind == .Enumerated_Array { 18 | id = reflect.typeid_elem(id) 19 | id = reflect.typeid_core(id) 20 | kind = reflect.type_kind(id) 21 | } 22 | 23 | #partial switch kind { 24 | case .Struct: 25 | res := true 26 | for ti in reflect.struct_field_types(id) { 27 | res &&= _can_be_bytes(ti.id) 28 | } 29 | return res 30 | 31 | case .Union: 32 | res := true 33 | for ti in type_info_of(id).variant.(reflect.Type_Info_Union).variants { 34 | res &&= _can_be_bytes(ti.id) 35 | } 36 | return res 37 | 38 | case .Slice, 39 | .Dynamic_Array, 40 | .Map, 41 | .Pointer, 42 | .Multi_Pointer, 43 | .String, 44 | .Procedure, 45 | .Type_Id, 46 | .Any, 47 | .Soa_Pointer, 48 | .Simd_Vector: 49 | return false 50 | } 51 | 52 | return true 53 | } 54 | 55 | /* Convert [dynamic] array to bytes. */ 56 | DynamicArrayToBytes :: proc(arr: $T/[dynamic]$E, loc := #caller_location) -> []u8 { 57 | if _can_be_bytes(E) { 58 | return DynamicArrayToBytesContextless(arr) 59 | } else { 60 | log.panicf("Cannot fully convert [dynamic] to bytes: %v", typeid_of(T), location = loc) 61 | } 62 | } 63 | 64 | /* Convert slice to bytes. */ 65 | SliceToBytes :: proc(s: $T/[]$E, loc := #caller_location) -> []u8 { 66 | if _can_be_bytes(E) { 67 | return SliceToBytesContextless(s) 68 | } else { 69 | log.panicf("Cannot fully convert [slice] to bytes: %v", typeid_of(T), location = loc) 70 | } 71 | } 72 | 73 | /* Convert any to bytes. */ 74 | AnyToBytes :: proc(v: any, loc := #caller_location) -> []u8 { 75 | if _can_be_bytes(v.id) { 76 | return AnyToBytesContextless(v) 77 | } else { 78 | log.panicf("Cannot fully convert [any] to bytes: %v", v.id, location = loc) 79 | } 80 | } 81 | } else { 82 | SliceToBytes :: SliceToBytesContextless 83 | DynamicArrayToBytes :: DynamicArrayToBytesContextless 84 | AnyToBytes :: AnyToBytesContextless 85 | } 86 | 87 | /* Compile time panic stub. */ 88 | MapToBytes :: proc "contextless" (m: $T/map[$K]$V) -> []u8 { 89 | #panic("Cannot fully convert [map] to bytes") 90 | } 91 | 92 | /* Convert buffer stream to bytes. */ 93 | BufferStreamToBytes :: #force_inline proc "contextless" ( 94 | b: bytes.Buffer, 95 | loc := #caller_location, 96 | ) -> []u8 #no_bounds_check { 97 | return b.buf[b.off:] 98 | } 99 | 100 | /* Convert [dynamic] array to bytes. */ 101 | DynamicArrayToBytesContextless :: #force_inline proc "contextless" ( 102 | arr: $T/[dynamic]$E, 103 | loc := #caller_location, 104 | ) -> []u8 { 105 | return slice.to_bytes(arr[:]) 106 | } 107 | 108 | /* Convert slice to bytes. */ 109 | SliceToBytesContextless :: #force_inline proc "contextless" ( 110 | s: $T/[]$E, 111 | loc := #caller_location, 112 | ) -> []u8 { 113 | return slice.to_bytes(s) 114 | } 115 | 116 | /* Convert any to bytes. */ 117 | AnyToBytesContextless :: #force_inline proc "contextless" ( 118 | v: any, 119 | loc := #caller_location, 120 | ) -> []u8 #no_bounds_check { 121 | sz: int 122 | if ti := type_info_of(v.id); ti != nil { 123 | sz = ti.size 124 | } 125 | return ([^]byte)(v.data)[:sz] 126 | } 127 | 128 | ToBytes :: proc { 129 | SliceToBytes, 130 | BufferStreamToBytes, 131 | DynamicArrayToBytes, 132 | MapToBytes, 133 | AnyToBytes, 134 | } 135 | 136 | ToBytesContextless :: proc { 137 | SliceToBytesContextless, 138 | BufferStreamToBytes, 139 | DynamicArrayToBytesContextless, 140 | MapToBytes, 141 | AnyToBytesContextless, 142 | } 143 | -------------------------------------------------------------------------------- /examples/cameras/wasd_camera.odin: -------------------------------------------------------------------------------- 1 | package cameras_example 2 | 3 | // Core 4 | import "core:math" 5 | import la "core:math/linalg" 6 | 7 | // WASD_Camera implements an FPS-style camera 8 | WASD_Camera :: struct { 9 | using camera: Camera, // Inherited from a base Camera struct 10 | pitch: f32, // Camera pitch angle (radians) 11 | yaw: f32, // Camera yaw angle (radians) 12 | velocity: la.Vector3f32, // Movement velocity 13 | movement_speed: f32, // Speed multiplier for movement 14 | rotation_speed: f32, // Speed multiplier for rotation 15 | friction_coefficient: f32, // Velocity drag coefficient [0..1] 16 | } 17 | 18 | // Creates a new WASD camera with optional position and target 19 | wasd_camera_create :: proc "contextless" ( 20 | position: la.Vector3f32, 21 | target: la.Vector3f32 = {}, 22 | ) -> ( 23 | camera: WASD_Camera, 24 | ) { 25 | camera.mat = la.MATRIX4F32_IDENTITY 26 | camera.view = la.MATRIX4F32_IDENTITY 27 | camera.movement_speed = 10 28 | camera.rotation_speed = 1 29 | camera.friction_coefficient = 0.99 30 | 31 | if position != {} || target != {} { 32 | pos := position == {} ? la.Vector3f32{0, 0, -5} : position 33 | tgt := target == {} ? la.Vector3f32{0, 0, 0} : target 34 | back := la.normalize(tgt - position) // Back is direction toward target 35 | wasd_camera_recalculate_angles(&camera, back) 36 | camera.position = pos 37 | } 38 | 39 | return camera 40 | } 41 | 42 | wasd_camera_update :: proc "contextless" ( 43 | camera: ^WASD_Camera, 44 | delta_time: f32, 45 | input: Input, 46 | ) -> la.Matrix4f32 { 47 | // Update rotation based on analog input 48 | camera.yaw -= input.analog.x * delta_time * camera.rotation_speed 49 | camera.pitch -= input.analog.y * delta_time * camera.rotation_speed 50 | 51 | // Wrap yaw and clamp pitch 52 | camera.yaw = math.mod(camera.yaw, 2 * math.PI) 53 | camera.pitch = math.clamp(camera.pitch, -math.PI / 2, math.PI / 2) 54 | 55 | // Store current position 56 | pos := camera.position.xyz 57 | 58 | // Rebuild rotation: forward direction based on yaw and pitch 59 | forward := la.Vector3f32 { 60 | math.sin(camera.yaw) * math.cos(camera.pitch), 61 | math.sin(camera.pitch), 62 | math.cos(camera.yaw) * math.cos(camera.pitch), 63 | } 64 | forward = la.normalize(forward) 65 | 66 | // Basis vectors: back is -forward (direction toward target) 67 | camera.back = -forward 68 | camera.right = la.normalize(la.vector_cross3(la.Vector3f32{0, 1, 0}, camera.back)) 69 | camera.up = la.normalize(la.vector_cross3(camera.back, camera.right)) 70 | 71 | // Calculate target velocity in world space 72 | digital := input.digital 73 | delta_right := b2f(digital.right) - b2f(digital.left) 74 | delta_up := b2f(digital.up) - b2f(digital.down) 75 | delta_back := b2f(digital.backward) - b2f(digital.forward) 76 | 77 | target_velocity := la.Vector3f32{0, 0, 0} 78 | target_velocity += delta_right * camera.right 79 | target_velocity += delta_up * camera.up 80 | target_velocity += delta_back * camera.back // Back is -forward, so this moves correctly 81 | if la.length(target_velocity) > 0 { 82 | target_velocity = la.normalize(target_velocity) 83 | } 84 | target_velocity *= camera.movement_speed 85 | 86 | // Update velocity with friction 87 | lerp_factor := math.pow(1 - camera.friction_coefficient, delta_time) 88 | camera.velocity = la.lerp(target_velocity, camera.velocity, lerp_factor) 89 | 90 | // Update position 91 | camera.position.xyz = pos + camera.velocity * delta_time 92 | 93 | // Build matrices 94 | update_matrix_from_vectors(camera) 95 | 96 | return camera.view 97 | } 98 | 99 | // Recalculates the yaw and pitch values from a directional vector 100 | wasd_camera_recalculate_angles :: proc "contextless" (camera: ^WASD_Camera, dir: la.Vector3f32) { 101 | camera.yaw = math.atan2(dir.x, dir.z) 102 | camera.pitch = math.asin(dir.y) 103 | } 104 | -------------------------------------------------------------------------------- /examples/learn_wgpu/beginner/tutorial3_pipeline/tutorial3_pipeline.odin: -------------------------------------------------------------------------------- 1 | package tutorial3_pipeline 2 | 3 | // Core 4 | import "core:log" 5 | 6 | // Local Packages 7 | import wgpu "../../../../" /* root folder */ 8 | import app "../../../../utils/application" 9 | 10 | CLIENT_WIDTH :: 640 11 | CLIENT_HEIGHT :: 480 12 | EXAMPLE_TITLE :: "Tutorial 3 - Pipeline" 13 | VIDEO_MODE_DEFAULT :: app.Video_Mode { 14 | width = CLIENT_WIDTH, 15 | height = CLIENT_HEIGHT, 16 | } 17 | 18 | Application :: struct { 19 | using _app: app.Application, /* #subtype */ 20 | render_pipeline: wgpu.RenderPipeline, 21 | rpass:struct { 22 | colors: [1]wgpu.RenderPassColorAttachment, 23 | descriptor: wgpu.RenderPassDescriptor, 24 | }, 25 | } 26 | 27 | init :: proc(self: ^Application) -> (ok: bool) { 28 | SHADER_WGSL :: #load("./shader.wgsl", string) 29 | shader_module := wgpu.DeviceCreateShaderModule( 30 | self.gpu.device, 31 | {source = SHADER_WGSL}, 32 | ) 33 | defer wgpu.Release(shader_module) 34 | 35 | render_pipeline_layout := wgpu.DeviceCreatePipelineLayout( 36 | self.gpu.device, 37 | {label = EXAMPLE_TITLE + " Layout"}, 38 | ) 39 | defer wgpu.Release(render_pipeline_layout) 40 | 41 | render_pipeline_descriptor := wgpu.RenderPipelineDescriptor { 42 | label = EXAMPLE_TITLE + " Render Pipeline", 43 | layout = render_pipeline_layout, 44 | vertex = {module = shader_module, entryPoint = "vs_main"}, 45 | fragment = &{ 46 | module = shader_module, 47 | entryPoint = "fs_main", 48 | targets = { 49 | { 50 | format = self.gpu.config.format, 51 | blend = &wgpu.BLEND_STATE_REPLACE, 52 | writeMask = wgpu.COLOR_WRITES_ALL, 53 | }, 54 | }, 55 | }, 56 | primitive = {topology = .TriangleList, frontFace = .CCW, cullMode = .Back}, 57 | multisample = {count = 1, mask = ~u32(0), alphaToCoverageEnabled = false}, 58 | } 59 | 60 | self.render_pipeline = wgpu.DeviceCreateRenderPipeline( 61 | self.gpu.device, 62 | render_pipeline_descriptor, 63 | ) 64 | 65 | self.rpass.colors[0] = { 66 | view = nil, /* Assigned later */ 67 | ops = {.Clear, .Store, {0.1, 0.2, 0.3, 1.0}}, 68 | } 69 | 70 | self.rpass.descriptor = { 71 | label = "Render pass descriptor", 72 | colorAttachments = self.rpass.colors[:], 73 | } 74 | 75 | return true 76 | } 77 | 78 | step :: proc(self: ^Application, dt: f32) -> (ok: bool) { 79 | frame := app.gpu_get_current_frame(self.gpu) 80 | if frame.skip { return } 81 | defer app.gpu_release_current_frame(&frame) 82 | 83 | encoder := wgpu.DeviceCreateCommandEncoder(self.gpu.device) 84 | defer wgpu.Release(encoder) 85 | 86 | self.rpass.colors[0].view = frame.view 87 | rpass := wgpu.CommandEncoderBeginRenderPass(encoder, self.rpass.descriptor) 88 | defer wgpu.Release(rpass) 89 | 90 | wgpu.RenderPassSetPipeline(rpass, self.render_pipeline) 91 | wgpu.RenderPassDraw(rpass, {0, 3}) 92 | 93 | wgpu.RenderPassEnd(rpass) 94 | 95 | cmdbuf := wgpu.CommandEncoderFinish(encoder) 96 | defer wgpu.Release(cmdbuf) 97 | 98 | wgpu.QueueSubmit(self.gpu.queue, { cmdbuf }) 99 | wgpu.SurfacePresent(self.gpu.surface) 100 | 101 | return true 102 | } 103 | 104 | event :: proc(self: ^Application, event: app.Event) -> (ok: bool) { 105 | #partial switch &ev in event { 106 | case app.Quit_Event: 107 | log.info("Exiting...") 108 | return 109 | } 110 | return true 111 | } 112 | 113 | quit :: proc(self: ^Application) { 114 | wgpu.Release(self.render_pipeline) 115 | } 116 | 117 | main :: proc() { 118 | when ODIN_DEBUG { 119 | context.logger = log.create_console_logger(opt = {.Level, .Terminal_Color}) 120 | defer log.destroy_console_logger(context.logger) 121 | } 122 | 123 | callbacks := app.Application_Callbacks{ 124 | init = app.App_Init_Callback(init), 125 | step = app.App_Step_Callback(step), 126 | event = app.App_Event_Callback(event), 127 | quit = app.App_Quit_Callback(quit), 128 | } 129 | 130 | app.init(Application, VIDEO_MODE_DEFAULT, EXAMPLE_TITLE, callbacks) 131 | } 132 | -------------------------------------------------------------------------------- /examples/microui/microui_example.odin: -------------------------------------------------------------------------------- 1 | package microui_example 2 | 3 | // Core 4 | import "core:log" 5 | 6 | // Vendor 7 | import mu "vendor:microui" 8 | 9 | // Local packages 10 | import wgpu "../.." 11 | import app "../../utils/application" 12 | import wgpu_mu "../../utils/microui" 13 | 14 | CLIENT_WIDTH :: 800 15 | CLIENT_HEIGHT :: 600 16 | EXAMPLE_TITLE :: "MicroUI Example" 17 | VIDEO_MODE_DEFAULT :: app.Video_Mode { 18 | width = CLIENT_WIDTH, 19 | height = CLIENT_HEIGHT, 20 | } 21 | 22 | Application :: struct { 23 | using _app: app.Application, 24 | mu_ctx: ^mu.Context, 25 | log_buf: [64000]u8, 26 | log_buf_len: int, 27 | log_buf_updated: bool, 28 | bg: mu.Color, 29 | rpass: struct { 30 | colors: [1]wgpu.RenderPassColorAttachment, 31 | descriptor: wgpu.RenderPassDescriptor, 32 | }, 33 | } 34 | 35 | init :: proc(self: ^Application) -> (ok: bool) { 36 | mu_init_info := wgpu_mu.MICROUI_INIT_INFO_DEFAULT 37 | mu_init_info.surface_config = self.gpu.config 38 | 39 | self.mu_ctx = new(mu.Context) 40 | 41 | mu.init(self.mu_ctx) 42 | self.mu_ctx.text_width = mu.default_atlas_text_width 43 | self.mu_ctx.text_height = mu.default_atlas_text_height 44 | 45 | // Initialize MicroUI context with default settings 46 | wgpu_mu.init(mu_init_info) 47 | 48 | // Set initial state 49 | self.bg = {56, 130, 210, 255} 50 | 51 | self.rpass.colors[0] = { 52 | view = nil, /* Assigned later */ 53 | ops = {.Clear, .Store, get_color_from_mu_color(self.bg)}, 54 | } 55 | 56 | self.rpass.descriptor = { 57 | label = "Render pass descriptor", 58 | colorAttachments = self.rpass.colors[:], 59 | } 60 | 61 | return true 62 | } 63 | 64 | step :: proc(self: ^Application, dt: f32) -> (ok: bool) { 65 | mu_update(self) 66 | 67 | frame := app.gpu_get_current_frame(self.gpu) 68 | if frame.skip { return } 69 | defer app.gpu_release_current_frame(&frame) 70 | 71 | encoder := wgpu.DeviceCreateCommandEncoder(self.gpu.device) 72 | defer wgpu.Release(encoder) 73 | 74 | self.rpass.colors[0].ops.clearValue = get_color_from_mu_color(self.bg) 75 | self.rpass.colors[0].view = frame.view 76 | rpass := wgpu.CommandEncoderBeginRenderPass(encoder, self.rpass.descriptor) 77 | defer wgpu.Release(rpass) 78 | 79 | wgpu_mu.render(self.mu_ctx, rpass) 80 | 81 | wgpu.RenderPassEnd(rpass) 82 | 83 | cmdbuf := wgpu.CommandEncoderFinish(encoder) 84 | defer wgpu.Release(cmdbuf) 85 | 86 | wgpu.QueueSubmit(self.gpu.queue, { cmdbuf }) 87 | wgpu.SurfacePresent(self.gpu.surface) 88 | 89 | return true 90 | } 91 | 92 | event :: proc(self: ^Application, event: app.Event) -> (ok: bool) { 93 | app.mu_handle_events(self.mu_ctx, event) 94 | #partial switch &ev in event { 95 | case app.Quit_Event: 96 | log.info("Exiting...") 97 | return 98 | case app.Resize_Event: 99 | resize(self, ev.size) 100 | } 101 | return true 102 | } 103 | 104 | quit :: proc(self: ^Application) { 105 | wgpu_mu.destroy() 106 | free(self.mu_ctx) 107 | } 108 | 109 | resize :: proc(self: ^Application, size: app.Vec2u) { 110 | wgpu_mu.resize(i32(size.x), i32(size.y)) 111 | } 112 | 113 | get_color_from_mu_color :: proc(color: mu.Color) -> wgpu.Color { 114 | return {f64(color.r) / 255.0, f64(color.g) / 255.0, f64(color.b) / 255.0, 1.0} 115 | } 116 | 117 | mu_update :: proc(self: ^Application) { 118 | // UI definition 119 | mu.begin(self.mu_ctx) 120 | test_window(self, self.mu_ctx) 121 | log_window(self, self.mu_ctx) 122 | style_window(self, self.mu_ctx) 123 | mu.end(self.mu_ctx) 124 | } 125 | 126 | main :: proc() { 127 | when ODIN_DEBUG { 128 | context.logger = log.create_console_logger(opt = {.Level, .Terminal_Color}) 129 | defer log.destroy_console_logger(context.logger) 130 | } 131 | 132 | callbacks := app.Application_Callbacks{ 133 | init = app.App_Init_Callback(init), 134 | step = app.App_Step_Callback(step), 135 | event = app.App_Event_Callback(event), 136 | quit = app.App_Quit_Callback(quit), 137 | } 138 | 139 | app.init(Application, VIDEO_MODE_DEFAULT, EXAMPLE_TITLE, callbacks) 140 | } 141 | -------------------------------------------------------------------------------- /examples/common/resources.odin: -------------------------------------------------------------------------------- 1 | #+build !js 2 | package examples_common 3 | 4 | // Core 5 | import "base:runtime" 6 | import "core:path/filepath" 7 | import "core:strings" 8 | import la "core:math/linalg" 9 | 10 | // Local packages 11 | import wgpu "../../" 12 | import app "../../utils/application" 13 | import "../../libs/tobj" 14 | 15 | load_model :: proc( 16 | ctx: ^app.Application, 17 | filename: string, 18 | layout: wgpu.BindGroupLayout, 19 | allocator := context.allocator, 20 | ) -> ( 21 | model: ^Model, 22 | ok: bool, 23 | ) #optional_ok { 24 | ta := context.temp_allocator 25 | runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == ta) 26 | 27 | obj_models, obj_materials, obj_err := tobj.load_obj(filename, allocator = ta) 28 | if obj_err != nil { 29 | tobj.print_error(obj_err) 30 | return 31 | } 32 | 33 | materials := make([dynamic]Material, allocator) 34 | mtl_dir := filepath.dir(filename, ta) 35 | for &m in obj_materials { 36 | mtl_filename := filepath.join({mtl_dir, m.diffuse_texture}, allocator = ta) 37 | diffuse_texture := app.create_texture_from_file(ctx, mtl_filename) or_return 38 | bind_group := wgpu.DeviceCreateBindGroup( 39 | ctx.gpu.device, 40 | { 41 | layout = layout, 42 | entries = { 43 | {binding = 0, resource = diffuse_texture.view}, 44 | {binding = 1, resource = diffuse_texture.sampler}, 45 | }, 46 | }, 47 | ) 48 | append( 49 | &materials, 50 | Material { 51 | allocator = allocator, 52 | name = strings.clone(m.name, allocator), 53 | diffuse_texture = diffuse_texture, 54 | bind_group = bind_group, 55 | }, 56 | ) 57 | } 58 | 59 | meshes := make([dynamic]Mesh, allocator) 60 | for &m in obj_models { 61 | vertices: [dynamic]Model_Vertex;vertices.allocator = ta 62 | for i in 0 ..< len(m.mesh.vertices) { 63 | pos := m.mesh.vertices[i] 64 | texture_coords: la.Vector2f32 65 | if len(m.mesh.texture_coords) > 0 { 66 | texture_coords = m.mesh.texture_coords[i] 67 | } 68 | normals: la.Vector3f32 69 | if len(m.mesh.normals) > 0 { 70 | normals = m.mesh.normals[i] 71 | } 72 | append(&vertices, Model_Vertex{pos, texture_coords, normals}) 73 | } 74 | 75 | vertex_buffer := wgpu.DeviceCreateBufferWithData( 76 | ctx.gpu.device, 77 | {contents = wgpu.ToBytes(vertices[:]), usage = {.Vertex}}, 78 | ) 79 | 80 | index_buffer := wgpu.DeviceCreateBufferWithData( 81 | ctx.gpu.device, 82 | {contents = wgpu.ToBytes(m.mesh.indices), usage = {.Index}}, 83 | ) 84 | 85 | mesh := Mesh { 86 | allocator = allocator, 87 | name = strings.clone(m.name, allocator), 88 | vertex_buffer = vertex_buffer, 89 | index_buffer = index_buffer, 90 | num_elements = u32(len(m.mesh.indices)), 91 | material_id = m.mesh.material_id, 92 | } 93 | 94 | append(&meshes, mesh) 95 | } 96 | 97 | model = new_clone(Model{allocator = allocator, meshes = meshes[:], materials = materials[:]}) 98 | assert(model != nil, "Failed to create model!") 99 | 100 | return model, true 101 | } 102 | 103 | destroy_mesh :: proc(mesh: Mesh) { 104 | context.allocator = mesh.allocator 105 | delete(mesh.name) 106 | wgpu.Release(mesh.vertex_buffer) 107 | wgpu.Release(mesh.index_buffer) 108 | } 109 | 110 | destroy_meshes :: proc(mesh: []Mesh) { 111 | if mesh == nil || len(mesh) == 0 { 112 | return 113 | } 114 | context.allocator = mesh[0].allocator 115 | for &m in mesh { 116 | destroy_mesh(m) 117 | } 118 | delete(mesh) 119 | } 120 | 121 | destroy_material :: proc(material: Material) { 122 | context.allocator = material.allocator 123 | delete(material.name) 124 | app.texture_release(material.diffuse_texture) 125 | wgpu.Release(material.bind_group) 126 | } 127 | 128 | destroy_materials :: proc(materials: []Material) { 129 | if materials == nil || len(materials) == 0 { 130 | return 131 | } 132 | context.allocator = materials[0].allocator 133 | for &m in materials { 134 | destroy_material(m) 135 | } 136 | delete(materials) 137 | } 138 | 139 | destroy_model :: proc(model: ^Model) { 140 | context.allocator = model.allocator 141 | destroy_materials(model.materials) 142 | destroy_meshes(model.meshes) 143 | free(model) 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WGPU Odin Bindings 2 | 3 | Bindings to [wgpu-native][] **25.0.2.1** for the [Odin Language][]. 4 | 5 | The API is similar to the [Rust API](https://docs.rs/wgpu/latest/wgpu/). This allows developers to 6 | easily translate Rust/WebGPU tutorials and examples into Odin code. 7 | 8 | Explore the [examples](./examples) to get started, or jump straight into the 9 | [Triangle](./examples/triangle) example. 10 | 11 | ## Status 12 | 13 | > [!NOTE] 14 | > The wgpu-API still in the "Working Draft" phase. Until the specification is stabilized, break 15 | > changes can happen without notice. 16 | 17 | ## Table of Contents 18 | 19 | - [Linking](#linking) 20 | - [Quick Start Guide](#quick-start-guide) 21 | - [Examples](#examples) 22 | - [Utilities](#utilities) 23 | - [Naming Conventions](#naming-conventions) 24 | - [License](#license) 25 | 26 | ## Linking 27 | 28 | This repository uses the raw bindings from `vendor`, make sure you read the `doc.odin` from 29 | `/vendor/wgpu/doc.odin` to learn more about how it works. 30 | 31 | ## Quick Start Guide 32 | 33 | 1. Create a folder named `libs/wgpu` in the root of your project (where you run `odin build` from). 34 | 35 | Alternatively, you can use the `shared/wgpu` folder in your Odin installation to later 36 | import the package in your code: 37 | 38 | ```odin 39 | import "shared:wgpu" 40 | ``` 41 | 42 | 2. Clone the contents of this repository in the folder created in the previous step, use the 43 | `--recursive` flag to fetch the assets/libs submodules for the examples: 44 | 45 | ```shell 46 | git clone --recursive https://github.com/Capati/wgpu-odin.git . 47 | ``` 48 | 49 | 3. Ensure you follow the [Linking](#linking) steps outlined above to include the appropriate 50 | binaries for your target platform. 51 | 52 | If you are not using the `shared` folder, to easily import the package into your project, set up a 53 | `collection` by adding this to your build command: 54 | 55 | ```shell 56 | odin build ./src -collection:libs=./libs 57 | ``` 58 | 59 | You can now import the package in your code: 60 | 61 | ```odin 62 | import "libs:wgpu" 63 | ``` 64 | 65 | ## Examples 66 | 67 | Explore [all examples](./examples) to see WGPU in action. Start with the 68 | [Triangle](./examples/triangle) for a verbose example. 69 | 70 | ## Utilities 71 | 72 | The [utils](./utils/) folder contains utility packages that enhance your WGPU development 73 | experience. Some of these packages are standalone and can be used independently without requiring 74 | any local package. 75 | 76 | - **application** (WIP): A lightweight framework for creating WGPU applications with a simple event 77 | loop, window management, and basic scene setup. Provides common boilerplate code to help you 78 | get started quickly. 79 | 80 | - **glfw/sdl**: Abstractions for surface creation. 81 | 82 | - **imgui** (WIP): A WGPU renderer implementation for the ImGui library. Provides bindings with 83 | backends implementations written in Odin. 84 | 85 | - **microui**: A WGPU renderer implementation for the microui library. 86 | 87 | - **shaders**: Common WGSL shader library functions. 88 | 89 | For detailed information about any specific utility, please refer to the README files in their 90 | respective directories. 91 | 92 | ## Naming Conventions 93 | 94 | Types and values follow the C API as close as possible. 95 | 96 | | Element | Convention | Example | 97 | |-------------------|----------------------|-------------------------| 98 | | Types | PascalCase | `TextureFormat` | 99 | | Enum Values | PascalCase | `HighPerformance` | 100 | | Procedures | PascalCase | `RenderPassEnd` | 101 | | Local Variables | snake_case | `buffer_descriptor` | 102 | | Struct Fields | camelCase | `sampleType` | 103 | | Constants | SCREAMING_SNAKE_CASE | `COPY_BUFFER_ALIGNMENT` | 104 | 105 | ## License 106 | 107 | MIT License - See [LICENSE](./LICENSE) file for details. 108 | 109 | [wgpu-native]: https://github.com/gfx-rs/wgpu-native 110 | [Odin Language]: https://odin-lang.org/ 111 | [OLS]: https://github.com/DanielGavin/ols 112 | -------------------------------------------------------------------------------- /utils.odin: -------------------------------------------------------------------------------- 1 | package webgpu 2 | 3 | /* 4 | Aligns the given size to the specified alignment. 5 | */ 6 | @(require_results) 7 | AlignSize :: #force_inline proc "contextless" (#any_int size, align: u64) -> u64 { 8 | return (size + (align - 1)) & ~(align - 1) 9 | } 10 | 11 | 12 | /* 13 | Check if the given value is aligned to the specified alignment. 14 | */ 15 | @(require_results) 16 | IsAligned :: #force_inline proc "contextless" (#any_int value, align: u64) -> bool { 17 | return (value & (align - 1)) == 0 18 | } 19 | 20 | /* 21 | location information for a vertex in a shader. 22 | */ 23 | VertexLocation :: struct { 24 | location: ShaderLocation, 25 | format: VertexFormat, 26 | } 27 | 28 | /* 29 | Create an array of `[N]VertexAttribute`, each element representing a vertex 30 | attribute with a specified shader location and format. The attributes' offsets 31 | are calculated automatically based on the size of each format. 32 | 33 | Arguments: 34 | 35 | - `N`: Compile-time constant specifying the number of vertex attributes. 36 | - `locations`: Specify the shader location and vertex format for each `N` locations. 37 | 38 | Example: 39 | 40 | attributes := wgpu.vertex_attr_array(2, {0, .Float32x4}, {1, .Float32x2}) 41 | 42 | Result in: 43 | 44 | attributes := []VertexAttribute { 45 | {format = .Float32x4, offset = 0, shader_location = 0}, 46 | { 47 | format = .Float32x2, 48 | offset = 16, 49 | shader_location = 1, 50 | }, 51 | }, 52 | 53 | **Notes**: 54 | 55 | - The number of locations provided must match the compile-time constant `N`. 56 | - Offsets are calculated automatically, assuming tightly packed attributes. 57 | */ 58 | VertexAttrArray :: proc "contextless" ( 59 | $N: int, 60 | locations: ..VertexLocation, 61 | ) -> ( 62 | attributes: [N]VertexAttribute, 63 | ) { 64 | assert_contextless(len(locations) == N, 65 | "Number of locations must match the generic parameter '$N'") 66 | 67 | offset: u64 = 0 68 | 69 | for v, i in locations { 70 | format := v.format 71 | attributes[i] = VertexAttribute { 72 | format = format, 73 | offset = offset, 74 | shaderLocation = v.location, 75 | } 76 | offset += VertexFormatGetSize(format) 77 | } 78 | 79 | return 80 | } 81 | 82 | /* 83 | Release a resource. 84 | */ 85 | Release :: proc { 86 | AdapterRelease, 87 | BindGroupRelease, 88 | BindGroupLayoutRelease, 89 | BufferRelease, 90 | CommandBufferRelease, 91 | CommandEncoderRelease, 92 | ComputePassRelease, 93 | ComputePipelineRelease, 94 | DeviceRelease, 95 | InstanceRelease, 96 | PipelineLayoutRelease, 97 | QuerySetRelease, 98 | QueueRelease, 99 | RenderBundleRelease, 100 | RenderBundleEncoderRelease, 101 | RenderPassRelease, 102 | RenderPipelineRelease, 103 | SamplerRelease, 104 | ShaderModuleRelease, 105 | SurfaceRelease, 106 | TextureRelease, 107 | TextureViewRelease, 108 | SurfaceTextureRelease, 109 | } 110 | 111 | /* 112 | Safely releases a resource. 113 | */ 114 | ReleaseSafe :: proc { 115 | AdapterReleaseSafe, 116 | BindGroupReleaseSafe, 117 | BindGroupLayoutReleaseSafe, 118 | BufferReleaseSafe, 119 | CommandBufferReleaseSafe, 120 | CommandEncoderReleaseSafe, 121 | ComputePassReleaseSafe, 122 | ComputePipelineReleaseSafe, 123 | DeviceReleaseSafe, 124 | InstanceReleaseSafe, 125 | PipelineLayoutReleaseSafe, 126 | QuerySetReleaseSafe, 127 | QueueReleaseSafe, 128 | RenderBundleReleaseSafe, 129 | RenderBundleEncoderReleaseSafe, 130 | RenderPassReleaseSafe, 131 | RenderPipelineReleaseSafe, 132 | SamplerReleaseSafe, 133 | ShaderModuleReleaseSafe, 134 | SurfaceReleaseSafe, 135 | TextureReleaseSafe, 136 | TextureViewReleaseSafe, 137 | SurfaceTextureReleaseSafe, 138 | } 139 | 140 | /* 141 | Increase the reference count of a resource. 142 | */ 143 | AddRef :: proc { 144 | AdapterAddRef, 145 | BindGroupAddRef, 146 | BindGroupLayoutAddRef, 147 | BufferAddRef, 148 | CommandBufferAddRef, 149 | CommandEncoderAddRef, 150 | ComputePassAddRef, 151 | ComputePipelineAddRef, 152 | DeviceAddRef, 153 | InstanceAddRef, 154 | PipelineLayoutAddRef, 155 | QuerySetAddRef, 156 | QueueAddRef, 157 | RenderBundleAddRef, 158 | RenderBundleEncoderAddRef, 159 | RenderPassAddRef, 160 | RenderPipelineAddRef, 161 | SamplerAddRef, 162 | ShaderModuleAddRef, 163 | SurfaceAddRef, 164 | TextureAddRef, 165 | TextureViewAddRef, 166 | } 167 | -------------------------------------------------------------------------------- /examples/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | :: Set default values 5 | set RELEASE_MODE=false 6 | set BUILD_TARGET=%1 7 | set ERROR_OCCURRED=false 8 | set RUN_AFTER_BUILD=false 9 | set CLEAN_BUILD=false 10 | set WEB_BUILD=false 11 | set ADDITIONAL_ARGS= 12 | 13 | :: Check for arguments 14 | set ARG_COUNTER=0 15 | for %%i in (%*) do ( 16 | if !ARG_COUNTER! equ 0 ( 17 | rem Skip the first argument 18 | ) else ( 19 | if /i "%%i"=="release" ( 20 | set RELEASE_MODE=true 21 | ) else if /i "%%i"=="run" ( 22 | set RUN_AFTER_BUILD=true 23 | ) else if /i "%%i"=="clean" ( 24 | set CLEAN_BUILD=true 25 | ) else if /i "%%i"=="web" ( 26 | set WEB_BUILD=true 27 | ) else ( 28 | set "ADDITIONAL_ARGS=!ADDITIONAL_ARGS! %%i" 29 | ) 30 | ) 31 | set /a ARG_COUNTER+=1 32 | ) 33 | 34 | :: Set mode string 35 | if "%RELEASE_MODE%"=="true" ( 36 | set MODE=RELEASE 37 | ) else ( 38 | set MODE=DEBUG 39 | ) 40 | 41 | :: Set build arguments based on target and mode 42 | if "%WEB_BUILD%"=="true" ( 43 | :: Web build arguments 44 | if "%RELEASE_MODE%"=="true" ( 45 | set ARGS=-o:size -disable-assert -no-bounds-check 46 | ) else ( 47 | set ARGS=-debug 48 | ) 49 | ) else ( 50 | :: Native build arguments 51 | if "%RELEASE_MODE%"=="true" ( 52 | set ARGS=-o:speed -disable-assert -no-bounds-check 53 | ) else ( 54 | set ARGS=-debug -define:WGPU_SHARED=true 55 | ) 56 | ) 57 | 58 | set OUT=.\build 59 | set OUT_FLAG=-out:%OUT% 60 | 61 | :: Check if a build target was provided 62 | if "%BUILD_TARGET%"=="" ( 63 | echo [BUILD] --- Error: Please provide a folder name to build 64 | echo [BUILD] --- Usage: build.bat folder_name [release] [run] [clean] [web] 65 | exit /b 1 66 | ) 67 | 68 | for %%i in ("%BUILD_TARGET:\=" "%") do set TARGET_NAME=%%~ni 69 | 70 | :: Clean build if requested 71 | if "%CLEAN_BUILD%"=="true" ( 72 | echo [BUILD] --- Cleaning artifacts... 73 | if exist "%OUT%\*.exe" del /F /Q %OUT%\*.exe 74 | if exist "%OUT%\*.pdb" del /F /Q %OUT%\*.pdb 75 | if exist "%OUT%\web\*.wasm" del /F /Q %OUT%\web\*.wasm 76 | if exist "%OUT%\web\wgpu.js" del /F /Q %OUT%\web\wgpu.js 77 | if exist "%OUT%\web\odin.js" del /F /Q %OUT%\web\odin.js 78 | ) 79 | 80 | set INITIAL_MEMORY_PAGES=2000 81 | set MAX_MEMORY_PAGES=65536 82 | set PAGE_SIZE=65536 83 | set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE% 84 | set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE% 85 | 86 | :: Get and set ODIN_ROOT environment variable 87 | for /f "delims=" %%i in ('odin.exe root') do set "ODIN_ROOT=%%i" 88 | set "ODIN_ROOT=%ODIN_ROOT:"=%" 89 | if "%ODIN_ROOT:~-1%"=="\" set "ODIN_ROOT=%ODIN_ROOT:~0,-1%" 90 | set ODIN_ROOT=%ODIN_ROOT% 91 | 92 | :: Handle web build 93 | if "%WEB_BUILD%"=="true" ( 94 | echo [BUILD] --- Building '%TARGET_NAME%' for web in %MODE% mode... 95 | call odin build .\%BUILD_TARGET% ^ 96 | %OUT_FLAG%\web\app.wasm ^ 97 | %ARGS% ^ 98 | -target:js_wasm32 ^ 99 | -extra-linker-flags:"--export-table --import-memory --initial-memory=!INITIAL_MEMORY_BYTES! --max-memory=!MAX_MEMORY_BYTES!" 100 | if errorlevel 1 ( 101 | echo [BUILD] --- Error building '%TARGET_NAME%' for web 102 | set ERROR_OCCURRED=true 103 | ) else ( 104 | copy "..\resources\wgpu.js" "%OUT%\web\wgpu.js" >nul 105 | copy "..\resources\odin.js" "%OUT%\web\odin.js" >nul 106 | echo [BUILD] --- Web build completed successfully. 107 | ) 108 | ) else ( 109 | :: Copy DLL if in debug mode 110 | if "%RELEASE_MODE%"=="false" ( 111 | if not exist "%OUT%\wgpu_native.dll" ( 112 | copy "%ODIN_ROOT%\vendor\wgpu\lib\wgpu-windows-x86_64-msvc-release\lib\wgpu_native.dll" "%OUT%\wgpu_native.dll" >nul 113 | ) 114 | ) 115 | 116 | :: Build the target (regular build) 117 | echo [BUILD] --- Building '%TARGET_NAME%' in %MODE% mode... 118 | call odin build .\%BUILD_TARGET% %ARGS% %ADDITIONAL_ARGS% %OUT_FLAG%\%TARGET_NAME%.exe 119 | if errorlevel 1 ( 120 | echo [BUILD] --- Error building '%TARGET_NAME%' 121 | set ERROR_OCCURRED=true 122 | ) 123 | ) 124 | 125 | if "%ERROR_OCCURRED%"=="true" ( 126 | echo [BUILD] --- Build process failed. 127 | exit /b 1 128 | ) else ( 129 | echo [BUILD] --- Build process completed successfully. 130 | if "%RUN_AFTER_BUILD%"=="true" ( 131 | if "%WEB_BUILD%"=="true" ( 132 | echo [BUILD] --- Note: Cannot automatically run web builds. Please open web/index.html in a browser. 133 | ) else ( 134 | echo [BUILD] --- Running %TARGET_NAME%... 135 | pushd build 136 | %TARGET_NAME%.exe 137 | popd 138 | ) 139 | ) 140 | exit /b 0 141 | ) 142 | -------------------------------------------------------------------------------- /utils/application/microui.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import intr "base:intrinsics" 5 | 6 | // Vendor 7 | import mu "vendor:microui" 8 | 9 | @(require_results) 10 | mu_mouse_button :: proc(button: Mouse_Button) -> (mu_mouse: mu.Mouse) { 11 | if button == .Left { 12 | mu_mouse = .LEFT 13 | } else if button == .Right { 14 | mu_mouse = .RIGHT 15 | } else if button == .Middle { 16 | mu_mouse = .MIDDLE 17 | } 18 | return 19 | } 20 | 21 | @(require_results) 22 | mu_key :: proc(key: Key) -> (mu_key: mu.Key) { 23 | if key == .Left_Shift || key == .Right_Shift { 24 | mu_key = .SHIFT 25 | } else if key == .Left_Control || key == .Right_Control { 26 | mu_key = .CTRL 27 | } else if key == .Left_Alt || key == .Right_Alt { 28 | mu_key = .ALT 29 | } else if key == .Backspace { 30 | mu_key = .BACKSPACE 31 | } else if key == .Enter { 32 | mu_key = .RETURN 33 | } 34 | return 35 | } 36 | 37 | mu_handle_events :: proc(mu_ctx: ^mu.Context, event: Event) { 38 | #partial switch &ev in event { 39 | // case Text_Input_Event: 40 | // mu.input_text(mu_ctx, string(cstring(&ev.buf[0]))) 41 | case Key_Pressed_Event: 42 | mu.input_key_down(mu_ctx, mu_key(ev.key)) 43 | 44 | case Key_Released_Event: 45 | mu.input_key_up(mu_ctx, mu_key(ev.key)) 46 | 47 | case Mouse_Button_Pressed_Event: 48 | mu.input_mouse_down( 49 | mu_ctx, 50 | i32(ev.pos.x), 51 | i32(ev.pos.y), 52 | mu_mouse_button(ev.button), 53 | ) 54 | 55 | case Mouse_Button_Released_Event: 56 | mu.input_mouse_up( 57 | mu_ctx, 58 | i32(ev.pos.x), 59 | i32(ev.pos.y), 60 | mu_mouse_button(ev.button), 61 | ) 62 | 63 | case Mouse_Wheel_Event: 64 | mu.input_scroll(mu_ctx, i32(ev.x * -25), i32(ev.y * -25)) 65 | 66 | case Mouse_Moved_Event: 67 | mu.input_mouse_move(mu_ctx, i32(ev.pos.x), i32(ev.pos.y)) 68 | } 69 | } 70 | 71 | Combobox_Item :: struct($T: typeid) where intr.type_is_enum(T) { 72 | item: T, 73 | name: string, 74 | } 75 | 76 | mu_combobox :: proc( 77 | ctx: ^mu.Context, 78 | name: string, 79 | current_item: ^$T, 80 | items: []Combobox_Item(T), 81 | ) -> mu.Result_Set where intr.type_is_enum(T) { 82 | id := mu.get_id(ctx, name) 83 | rect := mu.layout_next(ctx) 84 | mu.update_control(ctx, id, rect) 85 | 86 | // Draw main combobox button 87 | mu.draw_control_frame(ctx, id, rect, .BUTTON) 88 | 89 | // Draw current selection 90 | text_rect := rect 91 | text_rect.w -= 20 92 | // Find the name for the current item 93 | current_name := "" 94 | for item in items { 95 | if item.item == current_item^ { 96 | current_name = item.name 97 | break 98 | } 99 | } 100 | mu.draw_control_text(ctx, current_name, text_rect, .TEXT) 101 | 102 | // Draw dropdown arrow 103 | arrow_rect := rect 104 | arrow_rect.x = rect.x + rect.w - 20 105 | arrow_rect.w = 20 106 | mu.draw_icon(ctx, .EXPANDED, arrow_rect, ctx.style.colors[.TEXT]) 107 | 108 | res: mu.Result_Set 109 | mouseover := mu.mouse_over(ctx, rect) 110 | 111 | // Initialize container before click check 112 | cnt := mu.get_container(ctx, name) 113 | if cnt.rect.w == 0 { // If container is newly created 114 | cnt.open = false 115 | } 116 | 117 | // Handle input for main button 118 | if .LEFT in ctx.mouse_pressed_bits && mouseover { 119 | cnt.open = !cnt.open 120 | if cnt.open { 121 | ctx.hover_root = cnt 122 | ctx.next_hover_root = cnt 123 | popup_rect := rect 124 | popup_rect.y += rect.h + ctx.style.padding - 4 125 | popup_rect.h = min(200, i32(len(items)) * 24) 126 | cnt.rect = popup_rect 127 | mu.bring_to_front(ctx, cnt) 128 | } 129 | } 130 | 131 | if mu.begin_popup(ctx, name) { 132 | defer mu.end_popup(ctx) 133 | win := mu.get_current_container(ctx) 134 | mu.draw_rect(ctx, mu.get_current_container(ctx).rect, ctx.style.colors[.BASE]) 135 | mu.layout_row(ctx, {rect.w - ctx.style.padding * 2}) 136 | 137 | for item in items { 138 | if .SUBMIT in mu.button(ctx, item.name) { 139 | current_item^ = item.item 140 | res += {.SUBMIT, .CHANGE} 141 | win.open = false 142 | } 143 | } 144 | } 145 | 146 | return res 147 | } 148 | 149 | mu_slider :: proc( 150 | ctx: ^mu.Context, 151 | val: ^$T, 152 | lo, hi: T, 153 | step: mu.Real = 0.0, 154 | fmt_string: string = mu.SLIDER_FMT, 155 | options: mu.Options = {.ALIGN_CENTER}, 156 | ) -> ( 157 | res: mu.Result_Set, 158 | ) where intr.type_is_numeric(T) { 159 | mu.push_id(ctx, uintptr(val)) 160 | 161 | @(static) tmp: mu.Real 162 | 163 | tmp = mu.Real(val^) 164 | res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), step, fmt_string, options) 165 | val^ = T(tmp) 166 | mu.pop_id(ctx) 167 | 168 | return 169 | } 170 | -------------------------------------------------------------------------------- /examples/cameras/arcball_camera.odin: -------------------------------------------------------------------------------- 1 | package cameras_example 2 | 3 | // Core 4 | import "core:math" 5 | import la "core:math/linalg" 6 | 7 | /* Implements an orbiting camera. */ 8 | Arcball_Camera :: struct { 9 | using camera: Camera, 10 | distance: f32, 11 | angular_velocity: f32, 12 | axis: la.Vector3f32, 13 | rotation_speed: f32, 14 | zoom_speed: f32, 15 | friction_coefficient: f32, 16 | } 17 | 18 | arcball_camera_create :: proc "contextless" (position: la.Vector3f32) -> (camera: Arcball_Camera) { 19 | // Initialize matrices 20 | camera.mat = la.MATRIX4F32_IDENTITY 21 | camera.view = la.MATRIX4F32_IDENTITY 22 | camera.rotation_speed = 1 23 | camera.zoom_speed = 0.1 24 | camera.friction_coefficient = 0.999 25 | 26 | // odinfmt: disable 27 | // Initialize basis vectors from identity matrix 28 | camera.right = {1, 0, 0} // First column of identity 29 | camera.up = {0, 1, 0} // Second column of identity 30 | camera.back = {0, 0, 1} // Third column of identity 31 | camera.position = {0, 0, 0} // Fourth column of identity 32 | // odinfmt: enable 33 | 34 | if position != {} { 35 | camera.position = position 36 | camera.distance = la.length(position) 37 | camera.back = la.normalize(position) 38 | arcball_camera_recalculate_basis(&camera) 39 | } 40 | 41 | return camera 42 | } 43 | 44 | /* Updates the camera based on user input and time delta. */ 45 | arcball_camera_update :: proc "contextless" ( 46 | self: ^Arcball_Camera, 47 | delta_time: f32, 48 | input: Input, 49 | ) -> la.Matrix4f32 { 50 | // Process rotation changes from user input and apply inertia 51 | if input.analog.touching { 52 | // Reset any existing angular velocity when user starts interacting 53 | self.angular_velocity = 0 54 | 55 | // Calculate rotation axis by combining horizontal (right) and vertical (up) movement 56 | // Negative Y movement creates more intuitive controls (moving up rotates down) 57 | movement := scale(self.right, input.analog.x) + scale(self.up, -input.analog.y) 58 | 59 | // Determine rotation axis through cross product with view direction (back) 60 | // This creates rotation perpendicular to both movement and view direction 61 | cross_product := la.vector_cross3(movement, self.back) 62 | magnitude := la.vector_length(cross_product) 63 | 64 | if magnitude > math.F32_EPSILON { 65 | // Normalize rotation axis and set angular velocity proportional to movement 66 | self.axis = la.vector_normalize(cross_product) 67 | self.angular_velocity = magnitude * self.rotation_speed 68 | } 69 | } else { 70 | // Apply friction when not touching to smoothly decelerate rotation 71 | // Uses exponential decay for natural-feeling slowdown 72 | self.angular_velocity *= la.pow(1 - self.friction_coefficient, delta_time) 73 | } 74 | 75 | // Apply rotation if angular velocity is significant 76 | rotation_angle := self.angular_velocity * delta_time 77 | if rotation_angle > math.F32_EPSILON { 78 | // Create rotation quaternion for smooth interpolation 79 | rotation := la.quaternion_angle_axis_f32(rotation_angle, self.axis) 80 | // Rotate view direction using quaternion to minimize error accumulation 81 | self.back = la.vector_normalize(la.quaternion128_mul_vector3(rotation, self.back)) 82 | // Ensure camera basis vectors remain orthonormal 83 | arcball_camera_recalculate_basis(self) 84 | } 85 | 86 | // Handle camera zoom based on input (typically mouse wheel) 87 | if input.analog.zoom != 0 { 88 | // Calculate zoom factor - positive zoom moves closer, negative moves away 89 | zoom_factor := 1 - input.analog.zoom * self.zoom_speed 90 | // Update and clamp distance to prevent getting too close or too far 91 | self.distance = math.clamp( 92 | self.distance * zoom_factor, 93 | 0.1, // min distance 94 | 100.0, // max distance 95 | ) 96 | } 97 | 98 | // Position camera along view direction at current distance 99 | self.position = scale(self.back, self.distance) 100 | // Update matrices used for rendering 101 | update_matrix_from_vectors(self) 102 | 103 | return self.view 104 | } 105 | 106 | /* Recalculates right and up vectors to maintain orthonormal basis. */ 107 | arcball_camera_recalculate_basis :: proc "contextless" (self: ^Arcball_Camera) { 108 | arcball_camera_recalculate_right(self) 109 | arcball_camera_recalculate_up(self) 110 | } 111 | 112 | /* Calculate right vector from world up and back. */ 113 | arcball_camera_recalculate_right :: proc "contextless" (camera: ^Arcball_Camera) { 114 | camera.right = la.normalize(la.cross(camera.up, camera.back)) 115 | } 116 | 117 | /* Calculate camera up from back and right. */ 118 | arcball_camera_recalculate_up :: proc "contextless" (camera: ^Arcball_Camera) { 119 | camera.up = la.normalize(la.cross(camera.back, camera.right)) 120 | } 121 | -------------------------------------------------------------------------------- /utils/application/timer.odin: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | // Core 4 | import "core:time" 5 | 6 | TIMER_NUM_SAMPLES :: 60 7 | 8 | Timer :: struct { 9 | start_time: time.Time, 10 | prev_frame_start: time.Tick, 11 | frame_start: time.Tick, 12 | frame_end: time.Tick, 13 | delta_time_ms: f64, 14 | fps: f64, 15 | sample_index: int, 16 | work_times: [TIMER_NUM_SAMPLES]f64, 17 | average_work_time: f64, 18 | margin_ms: f64, 19 | target_frame_time_ms: f64, 20 | fps_update: bool, 21 | frame_count: uint, 22 | last_fps_update_time: f64, 23 | } 24 | 25 | timer_init :: proc "contextless" (t: ^Timer, margin_ms: f64, target_frame: f64) { 26 | assert_contextless(t != nil) 27 | 28 | when ODIN_OS == .Windows { 29 | _win32_timer_init(t) 30 | } 31 | 32 | t.start_time = time.now() 33 | t.prev_frame_start = {} 34 | t.frame_start = {} 35 | t.frame_end = {} 36 | t.delta_time_ms = 0.0 37 | t.fps = 0.0 38 | t.sample_index = 0 39 | t.average_work_time = 2.0 // Initial guess (ms) 40 | t.margin_ms = margin_ms 41 | t.target_frame_time_ms = target_frame 42 | t.work_times = {} 43 | t.fps_update = false 44 | t.frame_count = 0 45 | t.last_fps_update_time = 0.0 46 | } 47 | 48 | /* 49 | Marks the beginning of a new frame. 50 | 51 | This procedure should be called at the start of each frame update cycle. It 52 | records the current timestamp and calculates delta time for the frame. 53 | */ 54 | timer_begin_frame :: proc "contextless" (t: ^Timer) { 55 | now := time.tick_now() 56 | current_time := timer_get_time(t) 57 | 58 | if t.prev_frame_start._nsec != 0 { // Check if prev_frame_start is set 59 | delta_duration := time.tick_diff(t.prev_frame_start, now) 60 | t.delta_time_ms = time.duration_milliseconds(delta_duration) 61 | 62 | // Only calculate FPS if we have a reasonable frame time 63 | if t.delta_time_ms > 0.0 && t.delta_time_ms < 100.0 { 64 | t.frame_count += 1 65 | 66 | // Update FPS every second using timer_get_time 67 | if current_time - t.last_fps_update_time >= 1.0 { 68 | t.fps = f64(t.frame_count) / (current_time - t.last_fps_update_time) 69 | t.fps_update = true 70 | 71 | // Reset for next measurement period 72 | t.frame_count = 0 73 | t.last_fps_update_time = current_time 74 | } else { 75 | t.fps_update = false 76 | } 77 | } 78 | } else { 79 | t.delta_time_ms = 0.0 80 | t.fps = 0.0 81 | t.fps_update = false 82 | t.frame_count = 0 83 | t.last_fps_update_time = current_time 84 | } 85 | 86 | t.prev_frame_start = now 87 | t.frame_start = now 88 | } 89 | 90 | /** 91 | Marks the end of a frame and performs timing calculations. 92 | 93 | This function should be called at the end of each frame update cycle. It 94 | computes the frame duration, calculates FPS, and updates performance metrics. 95 | */ 96 | timer_end_frame :: proc "contextless" (t: ^Timer) #no_bounds_check { 97 | t.frame_end = time.tick_now() 98 | 99 | work_duration := time.tick_diff(t.frame_start, t.frame_end) 100 | work_time_ms := time.duration_milliseconds(work_duration) 101 | 102 | t.work_times[t.sample_index] = work_time_ms 103 | t.sample_index = (t.sample_index + 1) % TIMER_NUM_SAMPLES 104 | 105 | t.average_work_time = 0.0 106 | for i in 0 ..< TIMER_NUM_SAMPLES { 107 | t.average_work_time += t.work_times[i] 108 | } 109 | t.average_work_time /= f64(TIMER_NUM_SAMPLES) 110 | 111 | t.fps_update = false 112 | } 113 | 114 | /* Gets the delta time since the last frame in seconds. */ 115 | @(require_results) 116 | timer_get_delta :: proc "contextless" (t: ^Timer) -> f64 { 117 | return t.delta_time_ms / 1000.0 118 | } 119 | 120 | /* Gets the delta time since the last frame in milliseconds. */ 121 | @(require_results) 122 | timer_get_delta_ms :: proc "contextless" (t: ^Timer) -> f64 { 123 | return t.delta_time_ms 124 | } 125 | 126 | /* Gets the current frames per second (FPS). */ 127 | @(require_results) 128 | timer_get_fps :: proc "contextless" (t: ^Timer) -> f64 { 129 | return t.fps 130 | } 131 | 132 | /* 133 | Gets the average time spent on frame work in milliseconds. 134 | 135 | This excludes any sleep time and represents only the actual processing time. 136 | */ 137 | @(require_results) 138 | timer_get_average_work_ms :: proc "contextless" (t: ^Timer) -> f64 { 139 | return t.average_work_time 140 | } 141 | 142 | /* Returns the precise amount of time since some time in the past. */ 143 | @(require_results) 144 | timer_get_time :: proc "contextless" (t: ^Timer) -> f64 { 145 | return time.duration_seconds(time.since(t.start_time)) 146 | } 147 | 148 | @(require_results) 149 | timer_get_fps_update :: proc "contextless" (t: ^Timer) -> bool { 150 | return t.fps_update 151 | } 152 | -------------------------------------------------------------------------------- /examples/triangle_msaa/triangle_msaa.odin: -------------------------------------------------------------------------------- 1 | package triangle_msaa 2 | 3 | // Core 4 | import "core:log" 5 | 6 | // Local packages 7 | import wgpu "../.." 8 | import app "../../utils/application" 9 | 10 | CLIENT_WIDTH :: 640 11 | CLIENT_HEIGHT :: 480 12 | EXAMPLE_TITLE :: "Triangle 4x MSAA" 13 | VIDEO_MODE_DEFAULT :: app.Video_Mode { 14 | width = CLIENT_WIDTH, 15 | height = CLIENT_HEIGHT, 16 | } 17 | SAMPLE_COUNT :: 4 // This value is guaranteed to be supported 18 | 19 | Application :: struct { 20 | using _app: app.Application, 21 | render_pipeline: wgpu.RenderPipeline, 22 | msaa_view: wgpu.TextureView, 23 | rpass: struct { 24 | color_attachments: [1]wgpu.RenderPassColorAttachment, 25 | descriptor: wgpu.RenderPassDescriptor, 26 | }, 27 | } 28 | 29 | init :: proc(self: ^Application) -> (ok: bool) { 30 | // Use the same shader from the triangle example 31 | TRIANGLE_WGSL :: #load("./../triangle/triangle.wgsl", string) 32 | shader_module := wgpu.DeviceCreateShaderModule( 33 | self.gpu.device, 34 | { 35 | label = EXAMPLE_TITLE + " Module", 36 | source = TRIANGLE_WGSL, 37 | }, 38 | ) 39 | defer wgpu.Release(shader_module) 40 | 41 | self.render_pipeline = wgpu.DeviceCreateRenderPipeline( 42 | self.gpu.device, 43 | { 44 | label = EXAMPLE_TITLE + " Render Pipeline", 45 | vertex = { module = shader_module, entryPoint = "vs_main" }, 46 | fragment = &{ 47 | module = shader_module, 48 | entryPoint = "fs_main", 49 | targets = { 50 | { 51 | format = self.gpu.config.format, 52 | blend = &wgpu.BLEND_STATE_NORMAL, 53 | writeMask = wgpu.COLOR_WRITES_ALL, 54 | }, 55 | }, 56 | }, 57 | multisample = {count = SAMPLE_COUNT, mask = max(u32)}, 58 | }, 59 | ) 60 | 61 | create_msaa_framebuffer(self) 62 | 63 | self.rpass.color_attachments[0] = { 64 | view = nil, /* Assigned later */ 65 | resolveTarget = nil, /* Assigned later */ 66 | ops = { 67 | load = .Clear, 68 | store = .Store, 69 | clearValue = app.Color_Black, 70 | }, 71 | } 72 | 73 | self.rpass.descriptor = { 74 | label = "Render pass descriptor", 75 | colorAttachments = self.rpass.color_attachments[:], 76 | } 77 | 78 | return true 79 | } 80 | 81 | step :: proc(self: ^Application, dt: f32) -> (ok: bool) { 82 | frame := app.gpu_get_current_frame(self.gpu) 83 | if frame.skip { return } 84 | defer app.gpu_release_current_frame(&frame) 85 | 86 | encoder := wgpu.DeviceCreateCommandEncoder(self.gpu.device) 87 | defer wgpu.Release(encoder) 88 | 89 | self.rpass.color_attachments[0].view = self.msaa_view 90 | self.rpass.color_attachments[0].resolveTarget = frame.view 91 | rpass := wgpu.CommandEncoderBeginRenderPass(encoder, self.rpass.descriptor) 92 | defer wgpu.Release(rpass) 93 | 94 | wgpu.RenderPassSetPipeline(rpass, self.render_pipeline) 95 | wgpu.RenderPassDraw(rpass, {0, 3}) 96 | wgpu.RenderPassEnd(rpass) 97 | 98 | cmdbuf := wgpu.CommandEncoderFinish(encoder) 99 | defer wgpu.Release(cmdbuf) 100 | 101 | wgpu.QueueSubmit(self.gpu.queue, { cmdbuf }) 102 | wgpu.SurfacePresent(self.gpu.surface) 103 | 104 | return true 105 | } 106 | 107 | event :: proc(self: ^Application, event: app.Event) -> (ok: bool) { 108 | #partial switch &ev in event { 109 | case app.Quit_Event: 110 | log.info("Exiting...") 111 | return 112 | case app.Resize_Event: 113 | resize(self, ev.size) 114 | } 115 | return true 116 | } 117 | 118 | quit :: proc(self: ^Application) { 119 | wgpu.Release(self.msaa_view) 120 | wgpu.Release(self.render_pipeline) 121 | } 122 | 123 | resize :: proc(self: ^Application, size: app.Vec2u) { 124 | wgpu.Release(self.msaa_view) 125 | create_msaa_framebuffer(self) 126 | } 127 | 128 | create_msaa_framebuffer :: proc(self: ^Application) { 129 | format_features := 130 | wgpu.TextureFormatGuaranteedFormatFeatures(self.gpu.config.format, self.gpu.features) 131 | 132 | size := app.window_get_size(self.window) 133 | 134 | texture_descriptor := wgpu.TextureDescriptor { 135 | size = { size.x, size.y, 1 }, 136 | mipLevelCount = 1, 137 | sampleCount = SAMPLE_COUNT, 138 | dimension = ._2D, 139 | format = self.gpu.config.format, 140 | usage = format_features.allowedUsages, 141 | } 142 | 143 | texture := wgpu.DeviceCreateTexture(self.gpu.device, texture_descriptor) 144 | defer wgpu.Release(texture) 145 | 146 | self.msaa_view = wgpu.TextureCreateView(texture) 147 | } 148 | 149 | main :: proc() { 150 | when ODIN_DEBUG { 151 | context.logger = log.create_console_logger(opt = {.Level, .Terminal_Color}) 152 | defer log.destroy_console_logger(context.logger) 153 | } 154 | 155 | callbacks := app.Application_Callbacks{ 156 | init = app.App_Init_Callback(init), 157 | step = app.App_Step_Callback(step), 158 | event = app.App_Event_Callback(event), 159 | quit = app.App_Quit_Callback(quit), 160 | } 161 | 162 | app.init(Application, VIDEO_MODE_DEFAULT, EXAMPLE_TITLE, callbacks) 163 | } 164 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 09-26-2025 4 | 5 | feat: initial WASM support. 6 | 7 | ### Added 8 | 9 | - Initial WASM support 10 | - Bindings is working fine, however, some examples rely on native features or contain code that is 11 | not yet supported in the web environment 12 | 13 | - Add `InstanceRequestAdapterSync` and `AdapterRequestDeviceSync` 14 | - This version returns the result only, no need to handle callbacks 15 | - Only available on native 16 | 17 | ### Changed 18 | 19 | - `InstanceRequestAdapter` and `AdapterRequestDevice` now requires a `CallbackInfo` argument 20 | - To support web, the user need to handle the callbacks 21 | 22 | - Refactor examples `application` to work on both native and web 23 | 24 | ## [25.0.2.1] - 09-21-2025 25 | 26 | feat: upgrade to WGPU 25.0.2.1. 27 | 28 | ### Added 29 | 30 | - `SDL3` utils for surface creation. 31 | 32 | ### Fixed 33 | 34 | - fix(adapter): initialization of `nativeLimits` in `AdapterRequestDevice`. 35 | 36 | - fix #19 (examples): mixed up path for tobj. 37 | 38 | ### Changed 39 | 40 | - Use the raw bindings from `vendor`. 41 | - Change package name to `webgpu` to avoid conflict 42 | 43 | - Change naming convention to `PascalCase` and `camelCase` to conform with `vendor` and upstream. 44 | - All other names are changed too, not just part of wgpu. 45 | 46 | - `InstanceRequestAdapter` and `AdapterRequestDevice` now returns a result struct. 47 | 48 | ```odin 49 | RequestAdapterResult :: struct { 50 | status: RequestAdapterStatus, 51 | message: string, 52 | adapter: Adapter, 53 | } 54 | 55 | RequestDeviceResult :: struct { 56 | status: RequestDeviceStatus, 57 | message: string, 58 | device: Device, 59 | } 60 | ``` 61 | 62 | - Improve leak report. 63 | 64 | - Refactor `LimitsCheck` 65 | 66 | - Replace `StringViewBuffer` with `StringView` (aka `string`). 67 | - Some wrapping around `StringView` are not need anymore 68 | - `AdapterGetInfo` does not need an allocator 69 | 70 | - All procedures uses the `"c"` calling convention by default when possible 71 | 72 | - `QueueSubmit` second parameters is now a slice of `CommandBuffer`'s 73 | 74 | - Refactor all examples with the latest changes. 75 | 76 | ### Removed 77 | 78 | - Remove the global error handling. 79 | - Procedures no longer returns an optional ok 80 | - Errors are now handled by the user 81 | - The wgpu-native treats errors as fatal by default 82 | 83 | - Remove `StringViewBuffer`. 84 | - The `StringView` are just Odin `string`s, hence no need for the wrapper 85 | 86 | - Remove `exports.odin` 87 | - Move the contents of `wgpu` folder to the root directory 88 | 89 | ## 2025-23-03 90 | 91 | feat: upgrade to 24.0.3.1. 92 | 93 | ### Changed 94 | 95 | **General**: 96 | 97 | - Refactor rendering loop to improve input latency, specially on Fifo present mode 98 | 99 | ## [24.0.0.2] - 2025-28-02 100 | 101 | feat: upgrade to 24.0.0.2. 102 | 103 | [Compare changes](https://github.com/gfx-rs/wgpu-native/compare/v24.0.0.1...v24.0.0.2). 104 | 105 | ### Fixed 106 | 107 | - Return status from `adapter_info` and `surface_get_capabilities`. 108 | 109 | ## [24.0.0.1] - 2025-22-02 110 | 111 | feat: upgrade to 24.0.0.1 and refactor core components 112 | 113 | > [!WARNING] 114 | > There may be more changes than those listed here. Please refer to the examples, as 115 | > they are always updated. 116 | 117 | [Compare changes](https://github.com/gfx-rs/wgpu-native/compare/v22.1.0.5...v24.0.0.1). 118 | 119 | You can see a [list of changes](https://github.com/gfx-rs/wgpu-native/pull/427) in wgpu-native 120 | since latest update. 121 | 122 | ### Added 123 | 124 | **General**: 125 | 126 | - Implement `Buffer_Slice` and `Buffer_View` 127 | - `export.odin` in the root directory to allow use the package directly, useful to avoid 128 | repeating the package name, specially for submodules 129 | - Utility procedure for WGPU Memory Leak Report 130 | 131 | **Utils**: 132 | 133 | - Create new `application` utils to replace `renderlink` framework 134 | - Now is using `GLFW` instead of `SDL2` 135 | - Tiny OBJ Loader 136 | - This is currently used for the examples, but it will eventually be migrated to a submodule. 137 | - WGPU backend for ImGui written in Odin 138 | 139 | **Examples**: 140 | 141 | - Coordinate System 142 | - Instanced Cube 143 | - Two Cubes 144 | - Square 145 | - learn_wgpu/beginner/tutorial7_instancing 146 | - learn_wgpu/beginner/tutorial7_instancing_challenge 147 | - learn_wgpu/beginner/tutorial8_depth 148 | - learn_wgpu/beginner/tutorial9_model_loading 149 | 150 | ### Changed 151 | 152 | **General**: 153 | 154 | - Merge wrapper with raw bindings 155 | - This means there is no separation of two bindings, only the wrapper now 156 | - If you need raw version, I recommend the official bindings 157 | - Disallow `do` 158 | - Change the config `WGPU_CHECK_TO_BYTES` in favor of `ODIN_DEBUG` 159 | 160 | **Examples**: 161 | 162 | - Refactor the build script to build examples automatically by using the target name 163 | - Also added `run` command for the build script 164 | - Remove Makefile in favor of build.sh 165 | 166 | ### Fixed 167 | 168 | **General**: 169 | 170 | - Fixed instances range for **draw** procedures 171 | 172 | ### Removed 173 | 174 | **General**: 175 | 176 | - Move queue extension procedures to `application` utils 177 | -------------------------------------------------------------------------------- /.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ], 8 | "build_systems": 9 | [ 10 | { 11 | "name": "Odin Run (Debug)", 12 | "working_dir": "${project_path}/examples", 13 | "file_regex": "^(.+)\\(([0-9]+):([0-9]+)\\) (.+)$", 14 | "shell_cmd": null, 15 | "target": "terminus_exec", 16 | "cancel": "terminus_cancel_build", 17 | "focus": true, 18 | "timeit": true, 19 | "variants": 20 | [ 21 | { 22 | "name": "cameras", 23 | "cmd": ["cmd", "/c", "build.bat", "cameras", "run"], 24 | }, 25 | { 26 | "name": "capture", 27 | "cmd": ["cmd", "/c", "build.bat", "capture", "run"], 28 | }, 29 | { 30 | "name": "clear_screen", 31 | "cmd": ["cmd", "/c", "build.bat", "clear_screen", "run"], 32 | }, 33 | { 34 | "name": "compute", 35 | "cmd": ["cmd", "/c", "build.bat", "compute", "run"], 36 | }, 37 | { 38 | "name": "coordinate_system", 39 | "cmd": ["cmd", "/c", "build.bat", "coordinate_system", "run"], 40 | }, 41 | { 42 | "name": "cube", 43 | "cmd": ["cmd", "/c", "build.bat", "cube", "run"], 44 | }, 45 | { 46 | "name": "cube_textured", 47 | "cmd": ["cmd", "/c", "build.bat", "cube_textured", "run"], 48 | }, 49 | { 50 | "name": "cubemap", 51 | "cmd": ["cmd", "/c", "build.bat", "cubemap", "run"], 52 | }, 53 | { 54 | "name": "fractal_cube", 55 | "cmd": ["cmd", "/c", "build.bat", "fractal_cube", "run"], 56 | }, 57 | { 58 | "name": "image_blur", 59 | "cmd": ["cmd", "/c", "build.bat", "image_blur", "run"], 60 | }, 61 | { 62 | "name": "imgui", 63 | "cmd": ["cmd", "/c", "build.bat", "imgui", "run"], 64 | }, 65 | { 66 | "name": "info", 67 | "cmd": ["cmd", "/c", "build.bat", "info", "run"], 68 | }, 69 | { 70 | "name": "instanced_cube", 71 | "cmd": ["cmd", "/c", "build.bat", "instanced_cube", "run"], 72 | }, 73 | { 74 | "name": "microui", 75 | "cmd": ["cmd", "/c", "build.bat", "microui", "run"], 76 | }, 77 | { 78 | "name": "rotating_cube", 79 | "cmd": ["cmd", "/c", "build.bat", "rotating_cube", "run"], 80 | }, 81 | { 82 | "name": "square", 83 | "cmd": ["cmd", "/c", "build.bat", "square", "run"], 84 | }, 85 | { 86 | "name": "stencil_triangles", 87 | "cmd": ["cmd", "/c", "build.bat", "stencil_triangles", "run"], 88 | }, 89 | { 90 | "name": "texture_arrays", 91 | "cmd": ["cmd", "/c", "build.bat", "texture_arrays", "run"], 92 | }, 93 | { 94 | "name": "textured_cube", 95 | "cmd": ["cmd", "/c", "build.bat", "textured_cube", "run"], 96 | }, 97 | { 98 | "name": "triangle", 99 | "cmd": ["cmd", "/c", "build.bat", "triangle", "run"], 100 | }, 101 | { 102 | "name": "triangle_msaa", 103 | "cmd": ["cmd", "/c", "build.bat", "triangle_msaa", "run"], 104 | }, 105 | { 106 | "name": "two_cubes", 107 | "cmd": ["cmd", "/c", "build.bat", "two_cubes", "run"], 108 | }, 109 | { 110 | "name": "tutorial2_surface_challenge", 111 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial2_surface_challenge", "run"], 112 | }, 113 | { 114 | "name": "tutorial3_pipeline", 115 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial3_pipeline", "run"], 116 | }, 117 | { 118 | "name": "tutorial3_pipeline_challenge", 119 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial3_pipeline_challenge", "run"], 120 | }, 121 | { 122 | "name": "tutorial4_buffer", 123 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial4_buffer", "run"], 124 | }, 125 | { 126 | "name": "tutorial4_buffer_challenge", 127 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial4_buffer_challenge", "run"], 128 | }, 129 | { 130 | "name": "tutorial5_textures", 131 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial5_textures", "run"], 132 | }, 133 | { 134 | "name": "tutorial5_textures_challenge", 135 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial5_textures_challenge", "run"], 136 | }, 137 | { 138 | "name": "tutorial6_uniforms", 139 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial6_uniforms", "run"], 140 | }, 141 | { 142 | "name": "tutorial7_instancing", 143 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial7_instancing", "run"], 144 | }, 145 | { 146 | "name": "tutorial7_instancing_challenge", 147 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial7_instancing_challenge", "run"], 148 | }, 149 | { 150 | "name": "tutorial8_depth", 151 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial8_depth", "run"], 152 | }, 153 | { 154 | "name": "tutorial9_model_loading", 155 | "cmd": ["cmd", "/c", "build.bat", "learn_wgpu/beginner/tutorial9_model_loading", "run"], 156 | }, 157 | ], 158 | } 159 | ], 160 | "settings": 161 | { 162 | "tab_size": 4, 163 | "translate_tabs_to_spaces": false, 164 | "auto_complete": false, 165 | "LSP": 166 | { 167 | "odin": 168 | { 169 | "enabled": true, 170 | }, 171 | }, 172 | }, 173 | } 174 | -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Exit on any error 3 | set -e 4 | 5 | # Set default values 6 | RELEASE_MODE=false 7 | BUILD_TARGET="$1" 8 | ERROR_OCCURRED=false 9 | RUN_AFTER_BUILD=false 10 | CLEAN_BUILD=false 11 | WEB_BUILD=false 12 | ADDITIONAL_ARGS="" 13 | 14 | # Check if a build target was provided 15 | if [ -z "$BUILD_TARGET" ]; then 16 | echo "[BUILD] --- Error: Please provide a folder name to build" 17 | echo "[BUILD] --- Usage: $0 folder_name [release] [run] [clean] [web]" 18 | exit 1 19 | fi 20 | 21 | # Extract target name (last part of path) 22 | TARGET_NAME=$(basename "$BUILD_TARGET") 23 | 24 | # Process remaining arguments (skip first argument) 25 | shift 26 | while [ $# -gt 0 ]; do 27 | case "$1" in 28 | release) 29 | RELEASE_MODE=true 30 | ;; 31 | run) 32 | RUN_AFTER_BUILD=true 33 | ;; 34 | clean) 35 | CLEAN_BUILD=true 36 | ;; 37 | web) 38 | WEB_BUILD=true 39 | ;; 40 | *) 41 | ADDITIONAL_ARGS="$ADDITIONAL_ARGS $1" 42 | ;; 43 | esac 44 | shift 45 | done 46 | 47 | # Set mode string 48 | if [ "$RELEASE_MODE" = true ]; then 49 | MODE="RELEASE" 50 | else 51 | MODE="DEBUG" 52 | fi 53 | 54 | # Set build arguments based on target and mode 55 | if [ "$WEB_BUILD" = true ]; then 56 | # Web build arguments 57 | if [ "$RELEASE_MODE" = true ]; then 58 | ARGS="-o:size -disable-assert -no-bounds-check" 59 | else 60 | ARGS="-debug" 61 | fi 62 | else 63 | # Native build arguments 64 | if [ "$RELEASE_MODE" = true ]; then 65 | ARGS="-o:speed -disable-assert -no-bounds-check" 66 | else 67 | ARGS="-debug -define:WGPU_SHARED=true" 68 | fi 69 | fi 70 | 71 | OUT="./build" 72 | OUT_FLAG="-out:$OUT" 73 | 74 | # Clean build if requested 75 | if [ "$CLEAN_BUILD" = true ]; then 76 | echo "[BUILD] --- Cleaning artifacts..." 77 | # Remove native build artifacts 78 | rm -f "$OUT"/*.o # Object files 79 | rm -f "$OUT"/*.a # Static libraries 80 | rm -f "$OUT"/*.so # Shared objects/dynamic libraries 81 | rm -f "$OUT"/*.so.* # Versioned shared objects 82 | rm -f "$OUT"/*.dylib # macOS dynamic libraries 83 | rm -f "$OUT"/*.out # Default executable name (a.out) 84 | rm -f "$OUT"/*~ # Backup files 85 | rm -rf "$OUT"/*.dSYM # macOS debug symbols directory 86 | # Remove web build artifacts 87 | rm -f "$OUT"/web/*.wasm 88 | rm -f "$OUT"/web/wgpu.js 89 | rm -f "$OUT"/web/odin.js 90 | fi 91 | 92 | # Web memory settings (matching the .bat file) 93 | INITIAL_MEMORY_PAGES=2000 94 | MAX_MEMORY_PAGES=65536 95 | PAGE_SIZE=65536 96 | INITIAL_MEMORY_BYTES=$((INITIAL_MEMORY_PAGES * PAGE_SIZE)) 97 | MAX_MEMORY_BYTES=$((MAX_MEMORY_PAGES * PAGE_SIZE)) 98 | 99 | # Get and set ODIN_ROOT environment variable 100 | ODIN_ROOT=$(odin root | tr -d '"') 101 | # Remove trailing slash if present 102 | ODIN_ROOT=${ODIN_ROOT%/} 103 | 104 | # Handle web build 105 | if [ "$WEB_BUILD" = true ]; then 106 | echo "[BUILD] --- Building '$TARGET_NAME' for web in $MODE mode..." 107 | 108 | # Create web directory if it doesn't exist 109 | mkdir -p "$OUT/web" 110 | 111 | if ! odin build "./$BUILD_TARGET" \ 112 | "$OUT_FLAG/web/app.wasm" \ 113 | $ARGS \ 114 | -target:js_wasm32 \ 115 | -extra-linker-flags:"--export-table --import-memory --initial-memory=$INITIAL_MEMORY_BYTES --max-memory=$MAX_MEMORY_BYTES" \ 116 | $ADDITIONAL_ARGS; then 117 | echo "[BUILD] --- Error building '$TARGET_NAME' for web" 118 | ERROR_OCCURRED=true 119 | else 120 | # Copy required JS files (adjust paths as needed for your setup) 121 | if [ -f "../resources/wgpu.js" ]; then 122 | cp "../resources/wgpu.js" "$OUT/web/wgpu.js" 123 | fi 124 | if [ -f "../resources/odin.js" ]; then 125 | cp "../resources/odin.js" "$OUT/web/odin.js" 126 | fi 127 | echo "[BUILD] --- Web build completed successfully." 128 | fi 129 | else 130 | # Copy shared library if in debug mode (Linux/macOS equivalent of DLL copying) 131 | if [ "$RELEASE_MODE" = false ]; then 132 | # Determine the appropriate shared library based on OS 133 | case "$(uname -s)" in 134 | Linux*) 135 | WGPU_LIB="$ODIN_ROOT/vendor/wgpu/lib/wgpu-linux-x86_64-gnu/lib/libwgpu_native.so" 136 | if [ -f "$WGPU_LIB" ] && [ ! -f "$OUT/libwgpu_native.so" ]; then 137 | cp "$WGPU_LIB" "$OUT/libwgpu_native.so" 138 | fi 139 | ;; 140 | Darwin*) 141 | WGPU_LIB="$ODIN_ROOT/vendor/wgpu/lib/wgpu-macos-universal/lib/libwgpu_native.dylib" 142 | if [ -f "$WGPU_LIB" ] && [ ! -f "$OUT/libwgpu_native.dylib" ]; then 143 | cp "$WGPU_LIB" "$OUT/libwgpu_native.dylib" 144 | fi 145 | ;; 146 | esac 147 | fi 148 | 149 | # Build the target (regular build) 150 | echo "[BUILD] --- Building '$TARGET_NAME' in $MODE mode..." 151 | if ! odin build "./$BUILD_TARGET" $ARGS $ADDITIONAL_ARGS "$OUT_FLAG/$TARGET_NAME"; then 152 | echo "[BUILD] --- Error building '$TARGET_NAME'" 153 | ERROR_OCCURRED=true 154 | fi 155 | fi 156 | 157 | if [ "$ERROR_OCCURRED" = true ]; then 158 | echo "[BUILD] --- Build process failed." 159 | exit 1 160 | else 161 | echo "[BUILD] --- Build process completed successfully." 162 | if [ "$RUN_AFTER_BUILD" = true ]; then 163 | if [ "$WEB_BUILD" = true ]; then 164 | echo "[BUILD] --- Note: Cannot automatically run web builds. Please open web/index.html in a browser." 165 | else 166 | echo "[BUILD] --- Running $TARGET_NAME..." 167 | (cd build && ./"$TARGET_NAME") 168 | fi 169 | fi 170 | exit 0 171 | fi 172 | --------------------------------------------------------------------------------