Last active
October 29, 2025 03:11
-
-
Save fearofcode/0fb8bec972e7d860493400624eb8cb14 to your computer and use it in GitHub Desktop.
wgpu Uniform buffers in Odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package vendor_wgpu_example_triangle | |
| import "core:time" | |
| import "base:runtime" | |
| import "core:fmt" | |
| import "core:math" | |
| import "vendor:wgpu" | |
| import "vendor:glfw" | |
| import "vendor:wgpu/glfwglue" | |
| import glm "core:math/linalg/glsl" | |
| state: struct { | |
| ctx: runtime.Context, | |
| window: glfw.WindowHandle, | |
| instance: wgpu.Instance, | |
| surface: wgpu.Surface, | |
| adapter: wgpu.Adapter, | |
| device: wgpu.Device, | |
| config: wgpu.SurfaceConfiguration, | |
| queue: wgpu.Queue, | |
| module: wgpu.ShaderModule, | |
| pipeline_layout: wgpu.PipelineLayout, | |
| pipeline: wgpu.RenderPipeline, | |
| uniform_buffer: wgpu.Buffer, | |
| bind_group_layout: wgpu.BindGroupLayout, | |
| bind_group: wgpu.BindGroup, | |
| } | |
| Uniforms :: struct { | |
| scale: glm.vec2, | |
| offset: glm.vec2 | |
| } | |
| start_tick: time.Tick | |
| init_glfw :: proc() { | |
| if !glfw.Init() { | |
| panic("[glfw] init failure") | |
| } | |
| glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) | |
| state.window = glfw.CreateWindow(800, 600, "WGPU Native Triangle", nil, nil) | |
| glfw.SetFramebufferSizeCallback(state.window, size_callback) | |
| glfw.SwapInterval(1) | |
| glfw.MakeContextCurrent(state.window) | |
| } | |
| get_framebuffer_size :: proc() -> (width, height: u32) { | |
| iw, ih := glfw.GetFramebufferSize(state.window) | |
| return u32(iw), u32(ih) | |
| } | |
| size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) { | |
| resize() | |
| } | |
| main :: proc() { | |
| start_tick = time.tick_now() | |
| state.ctx = context | |
| init_glfw() | |
| state.instance = wgpu.CreateInstance(nil) | |
| if state.instance == nil { | |
| panic("WebGPU is not supported") | |
| } | |
| state.surface = glfwglue.GetSurface(state.instance, state.window) | |
| wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, { callback = on_adapter }) | |
| on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: string, userdata1: rawptr, userdata2: rawptr) { | |
| context = state.ctx | |
| if status != .Success || adapter == nil { | |
| fmt.panicf("request adapter failure: [%v] %s", status, message) | |
| } | |
| state.adapter = adapter | |
| wgpu.AdapterRequestDevice(adapter, nil, { callback = on_device }) | |
| } | |
| on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: string, userdata1: rawptr, userdata2: rawptr) { | |
| context = state.ctx | |
| if status != .Success || device == nil { | |
| fmt.panicf("request device failure: [%v] %s", status, message) | |
| } | |
| state.device = device | |
| width, height := get_framebuffer_size() | |
| state.config = wgpu.SurfaceConfiguration { | |
| device = state.device, | |
| usage = { .RenderAttachment }, | |
| format = .BGRA8UnormSrgb, | |
| width = width, | |
| height = height, | |
| presentMode = .Fifo, | |
| alphaMode = .Opaque, | |
| } | |
| wgpu.SurfaceConfigure(state.surface, &state.config) | |
| state.queue = wgpu.DeviceGetQueue(state.device) | |
| shader :: string(#load("shader.wgsl")) | |
| state.module = wgpu.DeviceCreateShaderModule(state.device, &{ | |
| nextInChain = &wgpu.ShaderSourceWGSL{ | |
| sType = .ShaderSourceWGSL, | |
| code = shader, | |
| }, | |
| }) | |
| bind_group_layout_entry := wgpu.BindGroupLayoutEntry { | |
| binding = 0, | |
| visibility = { .Vertex }, // This uniform is only used in the vertex shader | |
| buffer = wgpu.BufferBindingLayout { | |
| type = .Uniform, | |
| }, | |
| } | |
| state.bind_group_layout = wgpu.DeviceCreateBindGroupLayout(state.device, &{ | |
| label = "Uniform Bind Group Layout", | |
| entryCount = 1, | |
| entries = &bind_group_layout_entry, | |
| }) | |
| state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{ | |
| bindGroupLayoutCount = 1, | |
| bindGroupLayouts = &state.bind_group_layout, | |
| }) | |
| state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{ | |
| layout = state.pipeline_layout, | |
| vertex = { | |
| module = state.module, | |
| entryPoint = "vs_main", | |
| }, | |
| fragment = &{ | |
| module = state.module, | |
| entryPoint = "fs_main", | |
| targetCount = 1, | |
| targets = &wgpu.ColorTargetState{ | |
| format = .BGRA8UnormSrgb, | |
| writeMask = wgpu.ColorWriteMaskFlags_All, | |
| }, | |
| }, | |
| primitive = { | |
| topology = .TriangleList, | |
| }, | |
| multisample = { | |
| count = 1, | |
| mask = 0xFFFFFFFF, | |
| }, | |
| }) | |
| uniform_buffer_size := size_of(Uniforms) | |
| state.uniform_buffer = wgpu.DeviceCreateBuffer(state.device, &{ | |
| usage = { .Uniform, .CopyDst }, | |
| size = u64(uniform_buffer_size), | |
| }) | |
| state.bind_group = wgpu.DeviceCreateBindGroup(device, &{ | |
| layout = state.bind_group_layout, | |
| entryCount = 1, | |
| entries = &wgpu.BindGroupEntry{ | |
| binding = 0, | |
| buffer = state.uniform_buffer, | |
| size = u64(size_of(Uniforms)), | |
| }, | |
| }) | |
| main_loop() | |
| } | |
| } | |
| main_loop :: proc() { | |
| dt: f32 | |
| elapsed := time.duration_milliseconds(time.tick_since(start_tick)) | |
| fmt.println("Application run time: ", elapsed, " ms") | |
| for !glfw.WindowShouldClose(state.window) { | |
| elapsed = time.duration_milliseconds(time.tick_since(start_tick)) | |
| start := time.tick_now() | |
| glfw.PollEvents() | |
| context = state.ctx | |
| total_seconds := f32(time.duration_seconds(time.tick_since(start_tick))) | |
| offset_t := f32(math.sin(total_seconds))*0.1 | |
| uniform_values := Uniforms { | |
| scale = glm.vec2{ 1.0, 1.0}, | |
| offset = glm.vec2{ offset_t, -0.2}, | |
| } | |
| wgpu.QueueWriteBuffer( | |
| queue=state.queue, | |
| buffer=state.uniform_buffer, | |
| bufferOffset=0, | |
| data=&uniform_values, | |
| size=size_of(uniform_values), | |
| ) | |
| surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface) | |
| switch surface_texture.status { | |
| case .SuccessOptimal, .SuccessSuboptimal: | |
| // All good, could handle suboptimal here. | |
| case .Timeout, .Outdated, .Lost: | |
| // Skip this frame, and re-configure surface. | |
| if surface_texture.texture != nil { | |
| wgpu.TextureRelease(surface_texture.texture) | |
| } | |
| resize() | |
| return | |
| case .OutOfMemory, .DeviceLost, .Error: | |
| // Fatal error | |
| fmt.panicf("get_current_texture status=%v", surface_texture.status) | |
| } | |
| defer wgpu.TextureRelease(surface_texture.texture) | |
| frame := wgpu.TextureCreateView(surface_texture.texture, nil) | |
| defer wgpu.TextureViewRelease(frame) | |
| command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil) | |
| defer wgpu.CommandEncoderRelease(command_encoder) | |
| render_pass_encoder := wgpu.CommandEncoderBeginRenderPass( | |
| command_encoder, &{ | |
| colorAttachmentCount = 1, | |
| colorAttachments = &wgpu.RenderPassColorAttachment{ | |
| view = frame, | |
| loadOp = .Clear, | |
| storeOp = .Store, | |
| depthSlice = wgpu.DEPTH_SLICE_UNDEFINED, | |
| clearValue = { 0, 0, 0, 1 }, | |
| }, | |
| }, | |
| ) | |
| wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline) | |
| wgpu.RenderPassEncoderSetBindGroup(render_pass_encoder, 0, state.bind_group) | |
| wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0) | |
| wgpu.RenderPassEncoderEnd(render_pass_encoder) | |
| wgpu.RenderPassEncoderRelease(render_pass_encoder) | |
| command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil) | |
| defer wgpu.CommandBufferRelease(command_buffer) | |
| wgpu.QueueSubmit(state.queue, { command_buffer }) | |
| wgpu.SurfacePresent(state.surface) | |
| dt = f32(time.duration_seconds(time.tick_since(start))) | |
| } | |
| finish() | |
| glfw.DestroyWindow(state.window) | |
| glfw.Terminate() | |
| } | |
| resize :: proc "c" () { | |
| context = state.ctx | |
| state.config.width, state.config.height = get_framebuffer_size() | |
| wgpu.SurfaceConfigure(state.surface, &state.config) | |
| } | |
| finish :: proc() { | |
| wgpu.BindGroupLayoutRelease(state.bind_group_layout) | |
| wgpu.BindGroupRelease(state.bind_group) | |
| wgpu.BufferRelease(state.uniform_buffer) | |
| wgpu.RenderPipelineRelease(state.pipeline) | |
| wgpu.PipelineLayoutRelease(state.pipeline_layout) | |
| wgpu.ShaderModuleRelease(state.module) | |
| wgpu.QueueRelease(state.queue) | |
| wgpu.DeviceRelease(state.device) | |
| wgpu.AdapterRelease(state.adapter) | |
| wgpu.SurfaceRelease(state.surface) | |
| wgpu.InstanceRelease(state.instance) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct OurVertexShaderOutput { | |
| @builtin(position) position: vec4f, | |
| @location(0) color: vec4f, | |
| }; | |
| struct Uniforms { | |
| scale: vec2f, | |
| offset: vec2f | |
| }; | |
| @group(0) @binding(0) var<uniform> uniforms: Uniforms; | |
| @vertex | |
| fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput { | |
| let pos = array( | |
| vec2f( 0.0, 0.5), // top center | |
| vec2f(-0.5, -0.5), // bottom left | |
| vec2f( 0.5, -0.5) // bottom right | |
| ); | |
| let color = array<vec4f, 3>( | |
| vec4f(1, 0, 0, 1), // red | |
| vec4f(0, 0, 1, 1), // blue | |
| vec4f(0, 1, 0, 1), // green | |
| ); | |
| var vsOutput: OurVertexShaderOutput; | |
| vsOutput.position = vec4f(pos[vertexIndex]*uniforms.scale + uniforms.offset, 0.0, 1.0); | |
| vsOutput.color = color[vertexIndex]; | |
| return vsOutput; | |
| } | |
| @fragment | |
| fn fs_main(fsInput: OurVertexShaderOutput) -> @location(0) vec4f { | |
| return fsInput.color; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment