335 lines
10 KiB
Odin
335 lines
10 KiB
Odin
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)
|
|
} |