diff --git a/src/core/settings.odin b/src/core/settings.odin index 4581cdd..37dabff 100644 --- a/src/core/settings.odin +++ b/src/core/settings.odin @@ -7,7 +7,7 @@ Settings :: struct { height: u16, title: string, mode: WindowMode, - refresh: u16, + refresh: u16, // 0 = unlimited vsync: bool, // TODO: // windowed resizable y/n diff --git a/src/main.odin b/src/main.odin index 0471a73..59b82c1 100644 --- a/src/main.odin +++ b/src/main.odin @@ -26,7 +26,7 @@ main :: proc() { defer platform.os_destroy() // Initialize GPU resources - platform.instance_init() + platform.instance_init(settings) defer platform.instance_destroy() api.init_once() diff --git a/src/platform/glfw.odin b/src/platform/glfw.odin index 04ca6b8..383f078 100644 --- a/src/platform/glfw.odin +++ b/src/platform/glfw.odin @@ -106,8 +106,7 @@ os_set_monitor :: proc(settings: core.Settings) { ) } - refresh := settings.vsync ? mode.refresh_rate : i32(settings.refresh) - if refresh == 0 do refresh = glfw.DONT_CARE + refresh := settings.refresh == 0 ? glfw.DONT_CARE : i32(settings.refresh) glfw.SetWindowMonitor( state.os.window, diff --git a/src/platform/wgpu.odin b/src/platform/wgpu.odin index 762ef5e..86e5c91 100644 --- a/src/platform/wgpu.odin +++ b/src/platform/wgpu.odin @@ -2,15 +2,20 @@ package platform import "base:runtime" import "core:fmt" +import "core:slice" import "wgpu:wgpu" +import "sindri:core" + // ----------------------------------------- +// Variables // Instance -> Surface -> Adapter -> Device -> Queue state: struct { ctx: runtime.Context, os: OS, + // ---------------------------------------- instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface surface: wgpu.Surface, // handle to a presentable surface (e.g. a window) adapter: wgpu.Adapter, // handle to physical GPU @@ -20,115 +25,42 @@ state: struct { module: wgpu.ShaderModule, pipeline_layout: wgpu.PipelineLayout, pipeline: wgpu.RenderPipeline, + // ---------------------------------------- + present_modes: []wgpu.PresentMode, + vsync: bool, } // ----------------------------------------- +// Constructor/destructor -instance_init :: proc() { +instance_init :: proc(settings: core.Settings) { state.ctx = context + wgpu.SetLogCallback(log_callback, nil) + wgpu.SetLogLevel(.Warn) + + // Instance state.instance = wgpu.CreateInstance(nil) if state.instance == nil { - panic("WebGPU is not supported") + panic("[wgpu] WebGPU is not supported") } + + // Surface state.surface = os_get_surface(state.instance) + // Adapter 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}) - } + // Present modes + get_present_modes() + state.vsync = settings.vsync - 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 - - state.queue = wgpu.DeviceGetQueue(state.device) - - width, height := os_get_framebuffer_size() - - state.config = wgpu.SurfaceConfiguration { - device = state.device, - usage = {.RenderAttachment}, - format = .BGRA8Unorm, - width = width, - height = height, - // https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html - presentMode = .Fifo, // .Fifo is essentially VSync - alphaMode = .Opaque, - } - wgpu.SurfaceConfigure(state.surface, &state.config) - - shader :: ` - @vertex - fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { - let x = f32(i32(in_vertex_index) - 1); - let y = f32(i32(in_vertex_index & 1u) * 2 - 1); - return vec4(x, y, 0.0, 1.0); - } - - @fragment - fn fs_main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); - }` - - state.module = wgpu.DeviceCreateShaderModule( - state.device, - &{ - nextInChain = &wgpu.ShaderSourceWGSL { - sType = .ShaderSourceWGSL, - code = shader, - }, - }, - ) - - state.pipeline_layout = wgpu.DeviceCreatePipelineLayout( - state.device, - &{}, - ) - 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 = .BGRA8Unorm, - writeMask = wgpu.ColorWriteMaskFlags_All, - }, - }, - primitive = {topology = .TriangleList}, - multisample = {count = 1, mask = 0xFFFFFFFF}, - }, - ) - } + // Device + wgpu.AdapterRequestDevice(state.adapter, nil, {callback = on_device}) } instance_destroy :: proc() { @@ -143,6 +75,16 @@ instance_destroy :: proc() { } // ----------------------------------------- +// Public functions + +log_callback :: proc "c" ( + level: wgpu.LogLevel, + message: string, + userdata: rawptr, +) { + context = state.ctx + fmt.eprintfln("[wgpu:%v] %v", level, message) +} resize :: proc "c" () { context = state.ctx @@ -173,7 +115,7 @@ frame :: proc "c" (dt: f32) { case .Error: // Fatal error fmt.panicf( - "[triangle] get_current_texture status=%v", + "[wgpu] triangle get_current_texture status=%v", surface_texture.status, ) } @@ -217,3 +159,129 @@ frame :: proc "c" (dt: f32) { wgpu.QueueSubmit(state.queue, {command_buffer}) wgpu.SurfacePresent(state.surface) } + +// ----------------------------------------- +// Private functions + +@(private = "file") +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("[wgpu] request adapter failure: [%v] %s", status, message) + } + state.adapter = adapter +} + +@(private = "file") +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("[wgpu] request device failure: [%v] %s", status, message) + } + state.device = device + + state.queue = wgpu.DeviceGetQueue(state.device) + + width, height := os_get_framebuffer_size() + + state.config = wgpu.SurfaceConfiguration { + device = state.device, + usage = {.RenderAttachment}, + format = .BGRA8Unorm, + width = width, + height = height, + presentMode = pick_present_mode(state.vsync), + alphaMode = .Opaque, + } + wgpu.SurfaceConfigure(state.surface, &state.config) + + shader :: ` + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + }` + + state.module = wgpu.DeviceCreateShaderModule( + state.device, + &{ + nextInChain = &wgpu.ShaderSourceWGSL { + sType = .ShaderSourceWGSL, + code = shader, + }, + }, + ) + + state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{}) + 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 = .BGRA8Unorm, + writeMask = wgpu.ColorWriteMaskFlags_All, + }, + }, + primitive = {topology = .TriangleList}, + multisample = {count = 1, mask = 0xFFFFFFFF}, + }, + ) +} + +@(private = "file") +get_present_modes :: proc() { + capabilities, status := wgpu.SurfaceGetCapabilities( + state.surface, + state.adapter, + ) + if status != .Success { + fmt.panicf("[wgpu] surface capabilities failure: [%v]", status) + } + state.present_modes = capabilities.presentModes[:capabilities.presentModeCount] + fmt.println("[wgpu] supported present modes", state.present_modes) +} + +@(private = "file") +pick_present_mode :: proc(vsync: bool) -> wgpu.PresentMode { + if len(state.present_modes) == 0 do return .Fifo + + // Mimmick wgpu auto Vsync behavior + // https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html + if vsync { + if slice.contains(state.present_modes, wgpu.PresentMode.FifoRelaxed) { + return .FifoRelaxed // Adaptive Vsync + } + } else { + if slice.contains(state.present_modes, wgpu.PresentMode.Immediate) { + return .Immediate // Vsync Off + } + if slice.contains(state.present_modes, wgpu.PresentMode.Mailbox) { + return .Mailbox // Fast Vsync + } + } + + return .Fifo // Vsync On +}