diff --git a/README.md b/README.md index 2143afd..1a06b48 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,4 @@ vendor - miniaudio - microui (temp) - box3d? +- build.sh -> build.odin diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/os_glfw.odin b/src/glfw.odin similarity index 98% rename from src/os_glfw.odin rename to src/glfw.odin index 6ce2a7f..151c3d4 100644 --- a/src/os_glfw.odin +++ b/src/glfw.odin @@ -39,7 +39,7 @@ os_run :: proc() { dt = f32(time.duration_seconds(time.tick_since(start))) } - finish() + instance_destroy() glfw.DestroyWindow(state.os.window) glfw.Terminate() diff --git a/src/main.odin b/src/main.odin index 4ef1dde..834382c 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,222 +1,13 @@ package sindri -import "base:runtime" import "core:fmt" -import "gram:chunks" -import "wgpu:wgpu" - VERSION :: #config(VERSION, "dev") // ----------------------------------------- -state: struct { - ctx: runtime.Context, - os: OS, - 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, -} - -// ----------------------------------------- - main :: proc() { fmt.println("hello world!") - state.ctx = context - - os_init() - - state.instance = wgpu.CreateInstance(nil) - if state.instance == nil { - panic("WebGPU is not supported") - } - state.surface = os_get_surface(state.instance) - - 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 := os_get_framebuffer_size() - - state.config = wgpu.SurfaceConfiguration { - device = state.device, - usage = {.RenderAttachment}, - format = .BGRA8Unorm, - width = width, - height = height, - presentMode = .Fifo, - alphaMode = .Opaque, - } - wgpu.SurfaceConfigure(state.surface, &state.config) - - state.queue = wgpu.DeviceGetQueue(state.device) - - 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}, - }, - ) - - os_run() - } -} - -resize :: proc "c" () { - context = state.ctx - - state.config.width, state.config.height = os_get_framebuffer_size() - wgpu.SurfaceConfigure(state.surface, &state.config) -} - -frame :: proc "c" (dt: f32) { - context = state.ctx - - 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 .Occluded: - // Window is occluded (e.g. minimized), skip this frame. - return - case .Error: - // Fatal error - fmt.panicf( - "[triangle] 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, 1, 0, 1}, - }, - }, - ) - - wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline) - 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) -} - -finish :: proc() { - 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) + instance_init() } diff --git a/src/wgpu.odin b/src/wgpu.odin new file mode 100644 index 0000000..27924b7 --- /dev/null +++ b/src/wgpu.odin @@ -0,0 +1,221 @@ +package sindri + +import "base:runtime" +import "core:fmt" + +import "wgpu:wgpu" + +// ----------------------------------------- + +// 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 + device: wgpu.Device, // open connection to a GPU + queue: wgpu.Queue, // handle to command queue on a Device + config: wgpu.SurfaceConfiguration, + module: wgpu.ShaderModule, + pipeline_layout: wgpu.PipelineLayout, + pipeline: wgpu.RenderPipeline, +} + +// ----------------------------------------- + +instance_init :: proc() { + state.ctx = context + + os_init() + + state.instance = wgpu.CreateInstance(nil) + if state.instance == nil { + panic("WebGPU is not supported") + } + state.surface = os_get_surface(state.instance) + + 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 + + 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}, + }, + ) + + os_run() + } +} + +instance_destroy :: proc() { + 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) +} + +// ----------------------------------------- + +resize :: proc "c" () { + context = state.ctx + + state.config.width, state.config.height = os_get_framebuffer_size() + wgpu.SurfaceConfigure(state.surface, &state.config) +} + +frame :: proc "c" (dt: f32) { + context = state.ctx + + 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 .Occluded: + // Window is occluded (e.g. minimized), skip this frame. + return + case .Error: + // Fatal error + fmt.panicf( + "[triangle] 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, 1, 0, 1}, + }, + }, + ) + + wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline) + 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) +}