package window import "base:runtime" import "core:strings" import "core:log" import "core:time" import "vendor:glfw" import "vendor:wgpu" import "vendor:wgpu/glfwglue" Config :: struct { title: string, width, height: int, resizable: bool, clear_color: wgpu.Color, } g_window: glfw.WindowHandle g_wgpu_surface: wgpu.Surface g_wgpu_queue: wgpu.Queue g_wgpu_device: wgpu.Device g_clear_color: wgpu.Color // CURRENT LIMITATION: Only one window can be created per application init :: proc(win_config: Config) -> bool { using win_config title_cstr, err := strings.clone_to_cstring(title, context.temp_allocator) if err != nil { log.errorf("Failed to translate title string to cstring") return false } if glfw.Init() == false { log.errorf("Failed to init GLFW") return false } glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) if !resizable { glfw.WindowHint(glfw.RESIZABLE, glfw.FALSE) } g_window = glfw.CreateWindow(i32(width), i32(height), title_cstr, nil, nil) if g_window == nil { log.errorf("Failed to create GLFW window") return false } inst_desc := wgpu.InstanceDescriptor{ nextInChain = nil } when ODIN_ARCH == .wasm32 { instance := wgpu.CreateInstance(nil) } else { instance := wgpu.CreateInstance(&inst_desc) } if instance == nil { log.errorf("Failed to initialize WebGPU instance") return false } log.debugf("WebGPU Instance: {0}", instance) g_wgpu_surface = glfwglue.GetSurface(instance, g_window) log.debugf("Requesting WebGPU Adapter...") adapter_opts: wgpu.RequestAdapterOptions adapter_opts.nextInChain = nil adapter_opts.compatibleSurface = g_wgpu_surface adapter := request_adapter_sync(instance, &adapter_opts) log.debugf("Got WebGPU Adapter: {0}", adapter) when ODIN_DEBUG { inspect_adapter(adapter) } log.debugf("Requesting WebGPU device...") device_desc := wgpu.DeviceDescriptor{ nextInChain = nil, label = "Powervessel Engine WebGPU Device", requiredFeatureCount = 0, requiredLimits = nil, defaultQueue = { nextInChain = nil, label = "Powervessel Engine Default Queue" }, deviceLostCallback = proc "cdecl" (reason: wgpu.DeviceLostReason, msg: cstring, raw_user_data: rawptr) { context = runtime.default_context() log.debugf("Device lost: reason {0}", reason) if msg != "" do log.debugf(" ({0})", msg) }, } g_wgpu_device = request_device_sync(adapter, &device_desc) log.debugf("Got WebGPU device: {0}", g_wgpu_device) on_device_error :: proc "cdecl" (type: wgpu.ErrorType, msg: cstring, raw_user_data: rawptr) { context = runtime.default_context() log.debugf("Uncaptured device error: type {0}", type) if msg != "" do log.debugf(" ({0})", msg) } wgpu.DeviceSetUncapturedErrorCallback(g_wgpu_device, on_device_error, nil) when ODIN_DEBUG { inspect_device(g_wgpu_device) } g_wgpu_queue = wgpu.DeviceGetQueue(g_wgpu_device) wgpu.QueueOnSubmittedWorkDone(g_wgpu_queue, proc "cdecl" (status: wgpu.QueueWorkDoneStatus, raw_user_data: rawptr) { context = runtime.default_context() log.debugf("Queued work finished with status: {0}", status) }) surface_format := wgpu.SurfaceGetPreferredFormat(g_wgpu_surface, adapter) surface_config := wgpu.SurfaceConfiguration{ nextInChain = nil, width = u32(width), height = u32(height), format = surface_format, viewFormatCount = 0, viewFormats = nil, usage = {.RenderAttachment}, device = g_wgpu_device, presentMode = .Fifo, alphaMode = .Auto, } wgpu.SurfaceConfigure(g_wgpu_surface, &surface_config) wgpu.AdapterRelease(adapter) g_clear_color = clear_color return true } update :: proc() -> bool { if glfw.WindowShouldClose(g_window) do return false glfw.PollEvents() rtv := get_render_target_view() if rtv == nil { log.fatalf("Failed to get render target view") return false } encoder_desc := wgpu.CommandEncoderDescriptor{ nextInChain = nil, label = "Powervessel Engine Command Encoder" } encoder := wgpu.DeviceCreateCommandEncoder(g_wgpu_device, &encoder_desc) color_attachment := wgpu.RenderPassColorAttachment{ view = rtv, resolveTarget = nil, loadOp = .Clear, storeOp = .Store, clearValue = g_clear_color } render_pass_desc := wgpu.RenderPassDescriptor{ nextInChain = nil, colorAttachmentCount = 1, colorAttachments = &color_attachment, depthStencilAttachment = nil, timestampWrites = nil, } render_pass := wgpu.CommandEncoderBeginRenderPass(encoder, &render_pass_desc) wgpu.RenderPassEncoderEnd(render_pass) wgpu.RenderPassEncoderRelease(render_pass) buffer_desc := wgpu.CommandBufferDescriptor{ nextInChain = nil, label = "Powervessel Engine Command Buffer" } buffer := wgpu.CommandEncoderFinish(encoder, &buffer_desc) wgpu.CommandEncoderRelease(encoder) wgpu.QueueSubmit(g_wgpu_queue, {buffer}) wgpu.CommandBufferRelease(buffer) wgpu.TextureViewRelease(rtv) when ODIN_ARCH != .wasm32 { wgpu.SurfacePresent(g_wgpu_surface) } return true } shutdown :: proc() { glfw.DestroyWindow(g_window) glfw.Terminate() } @(private) request_adapter_sync :: proc(instance: wgpu.Instance, opts: ^wgpu.RequestAdapterOptions) -> wgpu.Adapter { User_Data :: struct { adapter: wgpu.Adapter, request_ended: bool, odin_ctx: runtime.Context } user_data: User_Data user_data.odin_ctx = context on_adapter_request_ended :: proc "cdecl" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, msg: cstring, raw_user_data: rawptr) { user_data := cast(^User_Data)(raw_user_data) context = user_data.odin_ctx if status == .Success { user_data.adapter = adapter } else { log.errorf("Failed to get WebGPU adapter: {0}", msg) } user_data.request_ended = true } wgpu.InstanceRequestAdapter(instance, opts, on_adapter_request_ended, rawptr(&user_data)) for !user_data.request_ended { time.sleep(100 * time.Millisecond) } assert(user_data.request_ended) return user_data.adapter } @(private) request_device_sync :: proc(adapter: wgpu.Adapter, desc: ^wgpu.DeviceDescriptor) -> wgpu.Device { User_Data :: struct { device: wgpu.Device, request_ended: bool, odin_ctx: runtime.Context } user_data: User_Data user_data.odin_ctx = context on_device_request_end :: proc "cdecl" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, msg: cstring, raw_user_data: rawptr) { user_data := cast(^User_Data)(raw_user_data) context = user_data.odin_ctx if status == .Success { user_data.device = device } else { log.errorf("Failed to get WebGPU device: {0}", msg) } user_data.request_ended = true } wgpu.AdapterRequestDevice(adapter, desc, on_device_request_end, rawptr(&user_data)) for !user_data.request_ended { time.sleep(100 * time.Millisecond) } assert(user_data.request_ended) return user_data.device } @(private) inspect_adapter :: proc(adapter: wgpu.Adapter) { when ODIN_ARCH != .wasm32 { limits, limits_ok := wgpu.AdapterGetLimits(adapter) if limits_ok { log.debugf("Adapter limits:") log.debugf(" - maxTextureDimension1D: {0}", limits.limits.maxTextureDimension1D) log.debugf(" - maxTextureDimension2D: {0}", limits.limits.maxTextureDimension2D) log.debugf(" - maxTextureDimension3D: {0}", limits.limits.maxTextureDimension3D) log.debugf(" - maxTextureArrayLayers: {0}", limits.limits.maxTextureArrayLayers) } } features := wgpu.AdapterEnumerateFeatures(adapter, context.temp_allocator) log.debugf("WebGPU Adapter Features:") for f in features { log.debugf(" - {0}", f) } properties := wgpu.AdapterGetProperties(adapter) log.debugf("WebGPU Adapter Properties:") log.debugf(" - vendorID: {0}", properties.vendorID) log.debugf(" - vendorName: {0}", properties.vendorName) log.debugf(" - architecture: {0}", properties.architecture) log.debugf(" - deviceID: {0}", properties.deviceID) log.debugf(" - name: {0}", properties.name) log.debugf(" - driverDescription: {0}", properties.driverDescription) log.debugf(" - adapterType: {0}", properties.adapterType) log.debugf(" - backendType: {0}", properties.backendType) } @(private) inspect_device :: proc(device: wgpu.Device) { features := wgpu.DeviceEnumerateFeatures(device, context.temp_allocator) log.debugf("WebGPU Device Features:") for f in features { log.debugf(" - {0}", f) } limits, limits_ok := wgpu.DeviceGetLimits(device) if limits_ok { log.debugf("Device limits:") log.debugf(" - maxTextureDimension1D: {0}", limits.limits.maxTextureDimension1D) log.debugf(" - maxTextureDimension2D: {0}", limits.limits.maxTextureDimension2D) log.debugf(" - maxTextureDimension3D: {0}", limits.limits.maxTextureDimension3D) log.debugf(" - maxTextureArrayLayers: {0}", limits.limits.maxTextureArrayLayers) } } @(private) get_render_target_view :: proc() -> wgpu.TextureView { surface_tex := wgpu.SurfaceGetCurrentTexture(g_wgpu_surface) if surface_tex.status != .Success { log.errorf("Failed to get surface texture: {0}", surface_tex.status) return nil } view_desc := wgpu.TextureViewDescriptor{ nextInChain = nil, label = "Powervessel Engine Surface Texture View", format = wgpu.TextureGetFormat(surface_tex.texture), dimension = ._2D, baseMipLevel = 0, mipLevelCount = 1, baseArrayLayer = 0, arrayLayerCount = 1, aspect = .All, } return wgpu.TextureCreateView(surface_tex.texture, &view_desc) }