├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------