| … | 16 unchanged lines hidden | ||
| 17 | 17 | current_pipeline: bk.Pipeline_Handle, | |
| 18 | 18 | frame_active: bool, | |
| 19 | 19 | render_pass_active: bool, | |
| 20 | + | headless: bool, | |
| 20 | 21 | push_constant_buffer: u32, | |
| 21 | 22 | buffers: [MAX_BUFFERS]GL_Buffer_Entry, | |
| 22 | 23 | textures: [MAX_TEXTURES]GL_Texture_Entry, | |
| … | 129 unchanged lines hidden | ||
| 152 | 153 | return {}, false | |
| 153 | 154 | } | |
| 154 | 155 | state.gl_ctx = gl_ctx | |
| 156 | + | state.headless = gl_ctx.headless | |
| 155 | 157 | state.width = width | |
| 156 | 158 | state.height = height | |
| 157 | 159 | state.default_render_pass = bk.Render_Pass_Handle(1) | |
| … | 157 unchanged lines hidden | ||
| … | 5 unchanged lines hidden | ||
| 6 | 6 | ||
| 7 | 7 | import bk ".." | |
| 8 | 8 | ||
| 9 | + | EGL_PBUFFER_BIT :: 0x0001 | |
| 10 | + | ||
| 9 | 11 | GL_Context :: struct { | |
| 10 | 12 | display: egl.Display, | |
| 11 | 13 | surface: egl.Surface, | |
| 12 | 14 | gl_ctx: egl.Context, | |
| 13 | 15 | width: u32, | |
| 14 | 16 | height: u32, | |
| 17 | + | headless: bool, | |
| 15 | 18 | } | |
| 16 | 19 | ||
| 17 | 20 | create_gl_context :: proc( | |
| … | 6 unchanged lines hidden | ||
| 24 | 27 | ) { | |
| 25 | 28 | display: egl.Display | |
| 26 | 29 | native_window: egl.NativeWindowType | |
| 30 | + | headless := false | |
| 27 | 31 | ||
| 28 | 32 | switch surface.kind { | |
| 29 | 33 | case .X11: | |
| … | 10 unchanged lines hidden | ||
| 40 | 44 | } | |
| 41 | 45 | display = egl.GetDisplay(egl.NativeDisplayType(surface.wayland.display)) | |
| 42 | 46 | native_window = egl.NativeWindowType(surface.wayland.surface) | |
| 43 | - | case .None, .Win32: | |
| 44 | - | log.error("gpu/opengl: Linux OpenGL backend requires X11 or Wayland surface") | |
| 47 | + | case .None: | |
| 48 | + | display = egl.GetPlatformDisplay(.SURFACELESS_MESA, nil, nil) | |
| 49 | + | native_window = nil | |
| 50 | + | headless = true | |
| 51 | + | case .Win32: | |
| 52 | + | log.error("gpu/opengl: Linux OpenGL backend cannot use Win32 surface") | |
| 45 | 53 | return {}, false | |
| 46 | 54 | } | |
| 47 | 55 | ||
| … | 14 unchanged lines hidden | ||
| 62 | 70 | return {}, false | |
| 63 | 71 | } | |
| 64 | 72 | ||
| 65 | - | config_attribs := [?]i32 { | |
| 73 | + | window_config_attribs := [?]i32 { | |
| 66 | 74 | egl.SURFACE_TYPE, | |
| 67 | 75 | egl.WINDOW_BIT, | |
| 68 | 76 | egl.RENDERABLE_TYPE, | |
| … | 10 unchanged lines hidden | ||
| 79 | 87 | 24, | |
| 80 | 88 | egl.NONE, | |
| 81 | 89 | } | |
| 90 | + | headless_config_attribs := [?]i32 { | |
| 91 | + | egl.SURFACE_TYPE, | |
| 92 | + | EGL_PBUFFER_BIT, | |
| 93 | + | egl.RENDERABLE_TYPE, | |
| 94 | + | egl.OPENGL_BIT, | |
| 95 | + | egl.RED_SIZE, | |
| 96 | + | 8, | |
| 97 | + | egl.GREEN_SIZE, | |
| 98 | + | 8, | |
| 99 | + | egl.BLUE_SIZE, | |
| 100 | + | 8, | |
| 101 | + | egl.ALPHA_SIZE, | |
| 102 | + | 8, | |
| 103 | + | egl.DEPTH_SIZE, | |
| 104 | + | 24, | |
| 105 | + | egl.NONE, | |
| 106 | + | } | |
| 82 | 107 | config: egl.Config | |
| 83 | 108 | config_count: i32 | |
| 84 | - | if !egl.ChooseConfig(display, &config_attribs[0], &config, 1, &config_count) || | |
| 85 | - | config_count == 0 { | |
| 109 | + | if headless { | |
| 110 | + | if !egl.ChooseConfig(display, &headless_config_attribs[0], &config, 1, &config_count) || | |
| 111 | + | config_count == 0 { | |
| 112 | + | log.error("gpu/opengl: no compatible surfaceless EGL config") | |
| 113 | + | egl.Terminate(display) | |
| 114 | + | return {}, false | |
| 115 | + | } | |
| 116 | + | } else if !egl.ChooseConfig(display, &window_config_attribs[0], &config, 1, &config_count) || | |
| 117 | + | config_count == 0 { | |
| 86 | 118 | log.error("gpu/opengl: no compatible EGL config") | |
| 87 | 119 | egl.Terminate(display) | |
| 88 | 120 | return {}, false | |
| 89 | 121 | } | |
| 90 | 122 | ||
| 91 | - | egl_surface := egl.CreateWindowSurface(display, config, native_window, nil) | |
| 92 | - | if egl_surface == egl.NO_SURFACE { | |
| 93 | - | log.error("gpu/opengl: eglCreateWindowSurface failed") | |
| 94 | - | egl.Terminate(display) | |
| 95 | - | return {}, false | |
| 123 | + | egl_surface := egl.NO_SURFACE | |
| 124 | + | if !headless { | |
| 125 | + | egl_surface = egl.CreateWindowSurface(display, config, native_window, nil) | |
| 126 | + | if egl_surface == egl.NO_SURFACE { | |
| 127 | + | log.error("gpu/opengl: eglCreateWindowSurface failed") | |
| 128 | + | egl.Terminate(display) | |
| 129 | + | return {}, false | |
| 130 | + | } | |
| 96 | 131 | } | |
| 97 | 132 | ||
| 98 | 133 | context_attribs := [?]i32 { | |
| … | 16 unchanged lines hidden | ||
| 115 | 150 | if !egl.MakeCurrent(display, egl_surface, egl_surface, gl_ctx) { | |
| 116 | 151 | log.error("gpu/opengl: eglMakeCurrent failed") | |
| 117 | 152 | egl.DestroyContext(display, gl_ctx) | |
| 118 | - | egl.DestroySurface(display, egl_surface) | |
| 153 | + | if egl_surface != egl.NO_SURFACE do egl.DestroySurface(display, egl_surface) | |
| 119 | 154 | egl.Terminate(display) | |
| 120 | 155 | return {}, false | |
| 121 | 156 | } | |
| 122 | 157 | ||
| 123 | - | egl.SwapInterval(display, 1) | |
| 158 | + | if !headless do egl.SwapInterval(display, 1) | |
| 124 | 159 | return { | |
| 125 | 160 | display = display, | |
| 126 | 161 | surface = egl_surface, | |
| 127 | 162 | gl_ctx = gl_ctx, | |
| 128 | 163 | width = width, | |
| 129 | 164 | height = height, | |
| 165 | + | headless = headless, | |
| 130 | 166 | }, | |
| 131 | 167 | true | |
| 132 | 168 | } | |
| … | 13 unchanged lines hidden | ||
| 146 | 182 | } | |
| 147 | 183 | ||
| 148 | 184 | swap_gl_context :: proc(ctx: ^GL_Context) -> bool { | |
| 149 | - | if ctx == nil || ctx.display == egl.NO_DISPLAY || ctx.surface == egl.NO_SURFACE do return false | |
| 185 | + | if ctx == nil || ctx.display == egl.NO_DISPLAY do return false | |
| 186 | + | if ctx.headless do return true | |
| 187 | + | if ctx.surface == egl.NO_SURFACE do return false | |
| 150 | 188 | return egl.SwapBuffers(ctx.display, ctx.surface) == egl.TRUE | |
| 151 | 189 | } | |
| 152 | 190 | ||
| … | 9 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 21 unchanged lines hidden | ||
| 22 | 22 | cmd_buffers: [bk.MAX_FRAMES_IN_FLIGHT]vk.CommandBuffer, | |
| 23 | 23 | frame_sync: [bk.MAX_FRAMES_IN_FLIGHT]gpu.Frame_Sync, | |
| 24 | 24 | current_frame: u32, | |
| 25 | + | headless: bool, | |
| 26 | + | headless_extent: bk.Extent, | |
| 25 | 27 | ||
| 26 | 28 | // Handle pools | |
| 27 | 29 | buffers: [MAX_BUFFERS]Vk_Buffer_Entry, | |
| … | 116 unchanged lines hidden | ||
| 144 | 146 | return {}, false | |
| 145 | 147 | } | |
| 146 | 148 | state.instance = inst | |
| 149 | + | state.headless = surface_desc.kind == .None | |
| 150 | + | state.headless_extent = {width, height} | |
| 147 | 151 | ||
| 152 | + | if state.headless { | |
| 153 | + | dev, dev_ok := gpu.create_device_headless(inst.instance) | |
| 154 | + | if !dev_ok { | |
| 155 | + | log.error("gpu/vk: failed to create headless Vulkan device") | |
| 156 | + | gpu.destroy_instance(&state.instance) | |
| 157 | + | free(state) | |
| 158 | + | g_vk = nil | |
| 159 | + | return {}, false | |
| 160 | + | } | |
| 161 | + | state.device = dev | |
| 162 | + | if !init_frame_resources_vk(state) { | |
| 163 | + | return {}, false | |
| 164 | + | } | |
| 165 | + | backend = make_backend_vk(state) | |
| 166 | + | bk.backend_initialized = true | |
| 167 | + | return backend, true | |
| 168 | + | } | |
| 169 | + | ||
| 148 | 170 | // Create surface | |
| 149 | 171 | surface, surface_ok := gpu.create_surface(surface_desc, inst.instance) | |
| 150 | 172 | if !surface_ok { | |
| … | 95 unchanged lines hidden | ||
| 246 | 268 | return {}, false | |
| 247 | 269 | } | |
| 248 | 270 | ||
| 249 | - | // Create command pool | |
| 271 | + | if !init_frame_resources_vk(state) { | |
| 272 | + | return {}, false | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | // Register default render pass (swapchain render pass) in pool | |
| 276 | + | rp_handle, rp_ok := alloc_render_pass_handle() | |
| 277 | + | if !rp_ok { | |
| 278 | + | log.error("gpu/vk: failed to allocate default render pass handle") | |
| 279 | + | shutdown_vk() | |
| 280 | + | return {}, false | |
| 281 | + | } | |
| 282 | + | state.render_passes[rp_handle].handle = state.swapchain.render_pass | |
| 283 | + | state.render_passes[rp_handle].active = true | |
| 284 | + | state.default_render_pass = rp_handle | |
| 285 | + | ||
| 286 | + | // Populate backend vtable | |
| 287 | + | backend = make_backend_vk(state) | |
| 288 | + | ||
| 289 | + | bk.backend_initialized = true | |
| 290 | + | return backend, true | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | @(private) | |
| 294 | + | init_frame_resources_vk :: proc(state: ^Vulkan_State) -> bool { | |
| 250 | 295 | pool, pool_ok := gpu.create_command_pool(&state.device, state.device.queue_families.graphics) | |
| 251 | 296 | if !pool_ok { | |
| 252 | 297 | log.error("gpu/vk: failed to create command pool") | |
| 253 | 298 | shutdown_vk() | |
| 254 | - | return {}, false | |
| 299 | + | return false | |
| 255 | 300 | } | |
| 256 | 301 | state.cmd_pool = pool | |
| 257 | 302 | ||
| 258 | - | // Allocate command buffers | |
| 259 | 303 | bufs, bufs_ok := gpu.allocate_command_buffers(&state.device, pool, bk.MAX_FRAMES_IN_FLIGHT) | |
| 260 | 304 | if !bufs_ok { | |
| 261 | 305 | log.error("gpu/vk: failed to allocate command buffers") | |
| 262 | 306 | shutdown_vk() | |
| 263 | - | return {}, false | |
| 307 | + | return false | |
| 264 | 308 | } | |
| 265 | 309 | for i in 0 ..< bk.MAX_FRAMES_IN_FLIGHT { | |
| 266 | 310 | state.cmd_buffers[i] = bufs[i] | |
| 267 | 311 | } | |
| 268 | 312 | ||
| 269 | - | // Create sync objects | |
| 270 | 313 | sync, sync_ok := gpu.create_sync_objects(&state.device) | |
| 271 | 314 | if !sync_ok { | |
| 272 | 315 | log.error("gpu/vk: failed to create sync objects") | |
| 273 | 316 | shutdown_vk() | |
| 274 | - | return {}, false | |
| 317 | + | return false | |
| 275 | 318 | } | |
| 276 | 319 | state.frame_sync = sync | |
| 277 | - | ||
| 278 | - | // Register default render pass (swapchain render pass) in pool | |
| 279 | - | rp_handle, rp_ok := alloc_render_pass_handle() | |
| 280 | - | if !rp_ok { | |
| 281 | - | log.error("gpu/vk: failed to allocate default render pass handle") | |
| 282 | - | shutdown_vk() | |
| 283 | - | return {}, false | |
| 284 | - | } | |
| 285 | - | state.render_passes[rp_handle].handle = state.swapchain.render_pass | |
| 286 | - | state.render_passes[rp_handle].active = true | |
| 287 | - | state.default_render_pass = rp_handle | |
| 320 | + | return true | |
| 321 | + | } | |
| 288 | 322 | ||
| 289 | - | // Populate backend vtable | |
| 290 | - | backend = bk.Backend { | |
| 323 | + | @(private) | |
| 324 | + | make_backend_vk :: proc(state: ^Vulkan_State) -> bk.Backend { | |
| 325 | + | return bk.Backend { | |
| 291 | 326 | capabilities = bk.implemented_base_capabilities( | |
| 292 | 327 | bk.MAX_COLOR_TARGETS, | |
| 293 | 328 | min( | |
| … | 1 unchanged lines hidden | ||
| 295 | 330 | u32(VULKAN_IMPLEMENTED_PUSH_CONSTANT_MAX_SIZE), | |
| 296 | 331 | ), | |
| 297 | 332 | ), | |
| 298 | - | ||
| 299 | - | // Lifecycle | |
| 300 | 333 | shutdown = shutdown_vk, | |
| 301 | 334 | wait_idle = wait_idle_vk, | |
| 302 | - | ||
| 303 | - | // Frame | |
| 304 | 335 | begin_frame = begin_frame_vk, | |
| 305 | 336 | end_frame = end_frame_vk, | |
| 306 | 337 | on_resize = on_resize_vk, | |
| 307 | 338 | get_extent = get_extent_vk, | |
| 308 | 339 | current_frame_index = get_current_frame, | |
| 309 | - | ||
| 310 | - | // Render pass | |
| 311 | 340 | begin_render_pass = begin_render_pass_vk, | |
| 312 | 341 | begin_default_pass = begin_default_pass_vk, | |
| 313 | 342 | end_render_pass = end_render_pass_vk, | |
| 314 | 343 | set_viewport = set_viewport_vk, | |
| 315 | 344 | set_scissor = set_scissor_vk, | |
| 316 | 345 | set_depth_bias = set_depth_bias_vk, | |
| 317 | - | ||
| 318 | - | // Pipeline | |
| 319 | 346 | create_graphics_pipeline = create_graphics_pipeline_vk, | |
| 320 | 347 | destroy_graphics_pipeline = destroy_graphics_pipeline_vk, | |
| 321 | 348 | bind_graphics_pipeline = bind_graphics_pipeline_vk, | |
| 322 | 349 | push_constants = push_constants_vk, | |
| 323 | - | ||
| 324 | - | // Buffers | |
| 325 | 350 | create_buffer = create_buffer_vk, | |
| 326 | 351 | create_buffer_staged = create_buffer_staged_vk, | |
| 327 | 352 | destroy_buffer = destroy_buffer_vk, | |
| … | 3 unchanged lines hidden | ||
| 331 | 356 | bind_vertex_buffer = bind_vertex_buffer_vk, | |
| 332 | 357 | bind_vertex_buffer_slot = bind_vertex_buffer_slot_vk, | |
| 333 | 358 | bind_index_buffer = bind_index_buffer_vk, | |
| 334 | - | ||
| 335 | - | // Textures | |
| 336 | 359 | create_texture = create_texture_vk, | |
| 337 | 360 | destroy_texture = destroy_texture_vk, | |
| 338 | 361 | read_texture_rgba8 = read_texture_rgba8_vk, | |
| 339 | - | ||
| 340 | - | // Samplers | |
| 341 | 362 | create_sampler = create_sampler_vk, | |
| 342 | 363 | destroy_sampler = destroy_sampler_vk, | |
| 343 | - | ||
| 344 | - | // Images | |
| 345 | 364 | create_image = create_image_vk, | |
| 346 | 365 | create_image_view = create_image_view_vk, | |
| 347 | 366 | destroy_image = destroy_image_vk, | |
| 348 | - | ||
| 349 | - | // Descriptors | |
| 350 | 367 | create_descriptor_set_layout = create_descriptor_set_layout_vk, | |
| 351 | 368 | destroy_descriptor_set_layout = destroy_descriptor_set_layout_vk, | |
| 352 | 369 | create_descriptor_pool = create_descriptor_pool_vk, | |
| … | 2 unchanged lines hidden | ||
| 355 | 372 | bind_descriptor_set = bind_descriptor_set_vk, | |
| 356 | 373 | update_descriptor_image = update_descriptor_image_vk, | |
| 357 | 374 | update_descriptor_buffer = update_descriptor_buffer_vk, | |
| 358 | - | ||
| 359 | - | // Render pass objects | |
| 360 | 375 | create_render_pass = create_render_pass_vk, | |
| 361 | 376 | destroy_render_pass = destroy_render_pass_vk, | |
| 362 | 377 | create_framebuffer = create_framebuffer_vk, | |
| 363 | 378 | destroy_framebuffer = destroy_framebuffer_vk, | |
| 364 | - | ||
| 365 | - | // Shaders | |
| 366 | 379 | create_shader_module = create_shader_module_vk, | |
| 367 | 380 | destroy_shader = destroy_shader_vk, | |
| 368 | - | ||
| 369 | - | // Draw | |
| 370 | 381 | draw = draw_vk, | |
| 371 | 382 | draw_indexed = draw_indexed_vk, | |
| 372 | 383 | draw_indirect = draw_indirect_vk, | |
| 373 | 384 | draw_indexed_indirect = draw_indexed_indirect_vk, | |
| 374 | - | ||
| 375 | - | // Compute | |
| 376 | 385 | create_compute_pipeline = create_compute_pipeline_vk, | |
| 377 | 386 | destroy_compute_pipeline = destroy_compute_pipeline_vk, | |
| 378 | 387 | bind_compute_pipeline = bind_compute_pipeline_vk, | |
| 379 | 388 | dispatch_compute = dispatch_compute_vk, | |
| 380 | 389 | compute_barrier = compute_barrier_vk, | |
| 381 | - | ||
| 382 | - | // Sync | |
| 383 | 390 | get_default_render_pass = get_default_render_pass_vk, | |
| 384 | 391 | get_depth_format = get_depth_format_vk, | |
| 385 | 392 | } | |
| 386 | - | ||
| 387 | - | bk.backend_initialized = true | |
| 388 | - | return backend, true | |
| 389 | 393 | } | |
| 390 | 394 | ||
| 391 | 395 | // --- Helper: get Vulkan command buffer from frame context --- | |
| … | 156 unchanged lines hidden | ||
| … | 153 unchanged lines hidden | ||
| 154 | 154 | // Wait for previous frame's fence | |
| 155 | 155 | vk.WaitForFences(g_vk.device.device, 1, &sync.in_flight, true, max(u64)) | |
| 156 | 156 | ||
| 157 | + | if g_vk.headless { | |
| 158 | + | vk.ResetFences(g_vk.device.device, 1, &sync.in_flight) | |
| 159 | + | vk.ResetCommandBuffer(cmd, {}) | |
| 160 | + | begin_info := vk.CommandBufferBeginInfo { | |
| 161 | + | sType = .COMMAND_BUFFER_BEGIN_INFO, | |
| 162 | + | } | |
| 163 | + | result := vk.BeginCommandBuffer(cmd, &begin_info) | |
| 164 | + | if result != .SUCCESS { | |
| 165 | + | log.errorf("gpu/vk: headless vkBeginCommandBuffer failed: %v", result) | |
| 166 | + | return {}, false | |
| 167 | + | } | |
| 168 | + | g_vk.frame_active = true | |
| 169 | + | return bk.Frame_Context{frame_index = frame}, true | |
| 170 | + | } | |
| 171 | + | ||
| 157 | 172 | // Acquire next swapchain image | |
| 158 | 173 | result := vk.AcquireNextImageKHR( | |
| 159 | 174 | g_vk.device.device, | |
| … | 51 unchanged lines hidden | ||
| 211 | 226 | return false | |
| 212 | 227 | } | |
| 213 | 228 | ||
| 229 | + | if g_vk.headless { | |
| 230 | + | submit_info := vk.SubmitInfo { | |
| 231 | + | sType = .SUBMIT_INFO, | |
| 232 | + | commandBufferCount = 1, | |
| 233 | + | pCommandBuffers = &cmd, | |
| 234 | + | } | |
| 235 | + | result = vk.QueueSubmit(g_vk.device.graphics_queue, 1, &submit_info, sync.in_flight) | |
| 236 | + | if result != .SUCCESS { | |
| 237 | + | log.errorf("gpu/vk: headless vkQueueSubmit failed: %v", result) | |
| 238 | + | } | |
| 239 | + | g_vk.frame_active = false | |
| 240 | + | g_vk.current_frame = (frame + 1) % bk.MAX_FRAMES_IN_FLIGHT | |
| 241 | + | return result == .SUCCESS | |
| 242 | + | } | |
| 243 | + | ||
| 214 | 244 | // Submit | |
| 215 | 245 | wait_stage := vk.PipelineStageFlags{.COLOR_ATTACHMENT_OUTPUT} | |
| 216 | 246 | submit_info := vk.SubmitInfo { | |
| … | 56 unchanged lines hidden | ||
| 273 | 303 | on_resize_vk :: proc(width, height: u32) { | |
| 274 | 304 | if g_vk == nil {return} | |
| 275 | 305 | if width == 0 || height == 0 {return} | |
| 306 | + | if g_vk.headless { | |
| 307 | + | g_vk.headless_extent = {width, height} | |
| 308 | + | return | |
| 309 | + | } | |
| 276 | 310 | ||
| 277 | 311 | vk.DeviceWaitIdle(g_vk.device.device) | |
| 278 | 312 | gpu.recreate_swapchain(&g_vk.swapchain, &g_vk.device, g_vk.surface, width, height) | |
| … | 7 unchanged lines hidden | ||
| 286 | 320 | ||
| 287 | 321 | get_extent_vk :: proc() -> bk.Extent { | |
| 288 | 322 | if g_vk == nil {return {}} | |
| 323 | + | if g_vk.headless do return g_vk.headless_extent | |
| 289 | 324 | return bk.Extent{width = g_vk.swapchain.extent.width, height = g_vk.swapchain.extent.height} | |
| 290 | 325 | } | |
| 291 | 326 | ||
| … | 6 unchanged lines hidden | ||
| 298 | 333 | ||
| 299 | 334 | get_depth_format_vk :: proc() -> bk.Format { | |
| 300 | 335 | if g_vk == nil {return .Undefined} | |
| 336 | + | if g_vk.headless {return .Undefined} | |
| 301 | 337 | return from_vk_format(g_vk.swapchain.depth_format) | |
| 302 | 338 | } | |
| 303 | 339 | ||
| … | 52 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 127 unchanged lines hidden | ||
| 128 | 128 | return dev, true | |
| 129 | 129 | } | |
| 130 | 130 | ||
| 131 | + | create_device_headless :: proc(instance: vk.Instance) -> (dev: Gpu_Device, ok: bool) { | |
| 132 | + | device_count: u32 | |
| 133 | + | vk.EnumeratePhysicalDevices(instance, &device_count, nil) | |
| 134 | + | if device_count == 0 { | |
| 135 | + | log.error("gpu/core: no Vulkan-capable GPU found") | |
| 136 | + | return {}, false | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | devices := make([]vk.PhysicalDevice, device_count, context.temp_allocator) | |
| 140 | + | vk.EnumeratePhysicalDevices(instance, &device_count, raw_data(devices)) | |
| 141 | + | ||
| 142 | + | best_score := -1 | |
| 143 | + | for d in devices { | |
| 144 | + | score := rate_device_headless(d) | |
| 145 | + | if score > best_score { | |
| 146 | + | best_score = score | |
| 147 | + | dev.physical_device = d | |
| 148 | + | } | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | if best_score < 0 { | |
| 152 | + | log.error("gpu/core: no suitable headless GPU found") | |
| 153 | + | return {}, false | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | vk.GetPhysicalDeviceProperties(dev.physical_device, &dev.properties) | |
| 157 | + | log.infof("gpu/core: selected headless GPU: %s", dev.properties.deviceName) | |
| 158 | + | ||
| 159 | + | dev.queue_families = find_queue_families_headless(dev.physical_device) | |
| 160 | + | if !dev.queue_families.has_graphics || !dev.queue_families.has_compute { | |
| 161 | + | log.error("gpu/core: headless GPU missing graphics or compute queue") | |
| 162 | + | return {}, false | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | unique_families: [2]u32 | |
| 166 | + | unique_count: u32 = 0 | |
| 167 | + | ||
| 168 | + | add_unique :: proc(arr: ^[2]u32, count: ^u32, val: u32) { | |
| 169 | + | for i in 0..<count^ { | |
| 170 | + | if arr[i] == val do return | |
| 171 | + | } | |
| 172 | + | arr[count^] = val | |
| 173 | + | count^ += 1 | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | add_unique(&unique_families, &unique_count, dev.queue_families.graphics) | |
| 177 | + | add_unique(&unique_families, &unique_count, dev.queue_families.compute) | |
| 178 | + | ||
| 179 | + | queue_priority: f32 = 1.0 | |
| 180 | + | queue_create_infos: [2]vk.DeviceQueueCreateInfo | |
| 181 | + | for i in 0..<unique_count { | |
| 182 | + | queue_create_infos[i] = vk.DeviceQueueCreateInfo{ | |
| 183 | + | sType = .DEVICE_QUEUE_CREATE_INFO, | |
| 184 | + | queueFamilyIndex = unique_families[i], | |
| 185 | + | queueCount = 1, | |
| 186 | + | pQueuePriorities = &queue_priority, | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | features := vk.PhysicalDeviceFeatures{ | |
| 191 | + | samplerAnisotropy = true, | |
| 192 | + | fillModeNonSolid = true, | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | device_info := vk.DeviceCreateInfo{ | |
| 196 | + | sType = .DEVICE_CREATE_INFO, | |
| 197 | + | queueCreateInfoCount = unique_count, | |
| 198 | + | pQueueCreateInfos = &queue_create_infos[0], | |
| 199 | + | enabledExtensionCount = u32(len(required_device_extensions)), | |
| 200 | + | ppEnabledExtensionNames = &required_device_extensions[0], | |
| 201 | + | pEnabledFeatures = &features, | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | result := vk.CreateDevice(dev.physical_device, &device_info, nil, &dev.device) | |
| 205 | + | if result != .SUCCESS { | |
| 206 | + | log.errorf("gpu/core: headless vkCreateDevice failed: %v", result) | |
| 207 | + | return {}, false | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | vk.load_proc_addresses_device(dev.device) | |
| 211 | + | vk.GetDeviceQueue(dev.device, dev.queue_families.graphics, 0, &dev.graphics_queue) | |
| 212 | + | vk.GetDeviceQueue(dev.device, dev.queue_families.compute, 0, &dev.compute_queue) | |
| 213 | + | dev.present_queue = nil | |
| 214 | + | dev.queue_families.present = dev.queue_families.graphics | |
| 215 | + | dev.queue_families.has_present = false | |
| 216 | + | ||
| 217 | + | return dev, true | |
| 218 | + | } | |
| 219 | + | ||
| 131 | 220 | destroy_device :: proc(dev: ^Gpu_Device) { | |
| 132 | 221 | if dev.device != nil { | |
| 133 | 222 | vk.DestroyDevice(dev.device, nil) | |
| … | 35 unchanged lines hidden | ||
| 169 | 258 | } | |
| 170 | 259 | ||
| 171 | 260 | @(private) | |
| 261 | + | find_queue_families_headless :: proc(device: vk.PhysicalDevice) -> Queue_Families { | |
| 262 | + | families: Queue_Families | |
| 263 | + | ||
| 264 | + | count: u32 | |
| 265 | + | vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, nil) | |
| 266 | + | props := make([]vk.QueueFamilyProperties, count, context.temp_allocator) | |
| 267 | + | vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, raw_data(props)) | |
| 268 | + | ||
| 269 | + | for p, i in props { | |
| 270 | + | idx := u32(i) | |
| 271 | + | if .GRAPHICS in p.queueFlags && !families.has_graphics { | |
| 272 | + | families.graphics = idx | |
| 273 | + | families.has_graphics = true | |
| 274 | + | } | |
| 275 | + | if .COMPUTE in p.queueFlags && !families.has_compute { | |
| 276 | + | families.compute = idx | |
| 277 | + | families.has_compute = true | |
| 278 | + | } | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | return families | |
| 282 | + | } | |
| 283 | + | ||
| 284 | + | @(private) | |
| 172 | 285 | rate_device :: proc(device: vk.PhysicalDevice, surface: vk.SurfaceKHR) -> int { | |
| 173 | 286 | props: vk.PhysicalDeviceProperties | |
| 174 | 287 | vk.GetPhysicalDeviceProperties(device, &props) | |
| … | 43 unchanged lines hidden | ||
| 218 | 331 | ||
| 219 | 332 | return score | |
| 220 | 333 | } | |
| 334 | + | ||
| 335 | + | @(private) | |
| 336 | + | rate_device_headless :: proc(device: vk.PhysicalDevice) -> int { | |
| 337 | + | props: vk.PhysicalDeviceProperties | |
| 338 | + | vk.GetPhysicalDeviceProperties(device, &props) | |
| 339 | + | ||
| 340 | + | families := find_queue_families_headless(device) | |
| 341 | + | if !families.has_graphics || !families.has_compute { | |
| 342 | + | return -1 | |
| 343 | + | } | |
| 344 | + | ext_count: u32 | |
| 345 | + | vk.EnumerateDeviceExtensionProperties(device, nil, &ext_count, nil) | |
| 346 | + | exts := make([]vk.ExtensionProperties, ext_count, context.temp_allocator) | |
| 347 | + | vk.EnumerateDeviceExtensionProperties(device, nil, &ext_count, raw_data(exts)) | |
| 348 | + | for required in required_device_extensions { | |
| 349 | + | found := false | |
| 350 | + | for &ext in exts { | |
| 351 | + | name := cstring(raw_data(&ext.extensionName)) | |
| 352 | + | if name == required { | |
| 353 | + | found = true | |
| 354 | + | break | |
| 355 | + | } | |
| 356 | + | } | |
| 357 | + | if !found { | |
| 358 | + | return -1 | |
| 359 | + | } | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | score := 0 | |
| 363 | + | if props.deviceType == .DISCRETE_GPU { | |
| 364 | + | score += 1000 | |
| 365 | + | } else if props.deviceType == .INTEGRATED_GPU { | |
| 366 | + | score += 100 | |
| 367 | + | } | |
| 368 | + | return score | |
| 369 | + | } | |
| … | 3 unchanged lines hidden | ||
| 4 | 4 | import "core:mem" | |
| 5 | 5 | import "core:os" | |
| 6 | 6 | ||
| 7 | - | import app "../../app" | |
| 8 | 7 | import bk "../../backend" | |
| 9 | 8 | import compiler "../../compiler" | |
| 10 | 9 | import gpu "../.." | |
| 10 | + | import proof "../parity_common" | |
| 11 | 11 | import ir "../../render_ir" | |
| 12 | 12 | import resource "../../resource" | |
| 13 | 13 | ||
| … | 8 unchanged lines hidden | ||
| 22 | 22 | } | |
| 23 | 23 | ||
| 24 | 24 | VERTEX_SHADER :: ` | |
| 25 | - | vertex main | |
| 26 | - | in position: vec3 @location(0) | |
| 27 | - | in normal: vec3 @location(1) | |
| 28 | - | in uv: vec2 @location(2) | |
| 29 | - | out position: vec4 @builtin(position) | |
| 30 | - | do | |
| 31 | - | position = vec4(position, 0.0, 1.0) | |
| 25 | + | @varying | |
| 26 | + | struct VertexOutput | |
| 27 | + | @builtin(position) position: vec4 | |
| 28 | + | @location(0) uv: vec2 | |
| 32 | 29 | end | |
| 30 | + | ||
| 31 | + | @entry(vertex) | |
| 32 | + | function main(@location(0) pos: vec3, @location(1) normal: vec3, @location(2) uv: vec2) -> VertexOutput | |
| 33 | + | return VertexOutput { | |
| 34 | + | position = vec4(pos, 1.0), | |
| 35 | + | uv = uv, | |
| 36 | + | } | |
| 37 | + | end | |
| 33 | 38 | ` | |
| 34 | 39 | ||
| 35 | 40 | INSTANCE_VERTEX_SHADER :: ` | |
| 36 | - | vertex main | |
| 37 | - | in position: vec3 @location(0) | |
| 38 | - | in normal: vec3 @location(1) | |
| 39 | - | in uv: vec2 @location(2) | |
| 40 | - | in instance_position: vec3 @location(3) | |
| 41 | - | in instance_normal: vec3 @location(4) | |
| 42 | - | in instance_uv: vec2 @location(5) | |
| 43 | - | in instance_color: vec4 @location(6) | |
| 44 | - | out position: vec4 @builtin(position) | |
| 45 | - | out color: vec4 | |
| 46 | - | do | |
| 47 | - | position = vec4(position + instance_position, 0.0, 1.0) | |
| 48 | - | out.color = instance_color | |
| 41 | + | @varying | |
| 42 | + | struct InstanceVertexOutput | |
| 43 | + | @builtin(position) position: vec4 | |
| 44 | + | @location(0) color: vec4 | |
| 49 | 45 | end | |
| 46 | + | ||
| 47 | + | @entry(vertex) | |
| 48 | + | function main( | |
| 49 | + | @location(0) pos: vec3, | |
| 50 | + | @location(1) normal: vec3, | |
| 51 | + | @location(2) uv: vec2, | |
| 52 | + | @location(3) instance_pos: vec3, | |
| 53 | + | @location(4) instance_normal: vec3, | |
| 54 | + | @location(5) instance_uv: vec2, | |
| 55 | + | @location(6) instance_color: vec4 | |
| 56 | + | ) -> InstanceVertexOutput | |
| 57 | + | return InstanceVertexOutput { | |
| 58 | + | position = vec4(pos + instance_pos, 1.0), | |
| 59 | + | color = instance_color, | |
| 60 | + | } | |
| 61 | + | end | |
| 50 | 62 | ` | |
| 51 | 63 | ||
| 52 | 64 | RED_FRAGMENT_SHADER :: ` | |
| 53 | - | fragment main | |
| 54 | - | out color: vec4 | |
| 55 | - | do | |
| 56 | - | color = vec4(240.0 / 255.0, 64.0 / 255.0, 64.0 / 255.0, 1.0) | |
| 65 | + | struct FragmentOutput | |
| 66 | + | color: vec4 | |
| 57 | 67 | end | |
| 68 | + | ||
| 69 | + | @entry(fragment) | |
| 70 | + | function main() -> FragmentOutput | |
| 71 | + | return FragmentOutput { | |
| 72 | + | color = vec4(240.0 / 255.0, 64.0 / 255.0, 64.0 / 255.0, 1.0), | |
| 73 | + | } | |
| 74 | + | end | |
| 58 | 75 | ` | |
| 59 | 76 | ||
| 60 | 77 | INSTANCE_FRAGMENT_SHADER :: ` | |
| 61 | - | fragment main | |
| 62 | - | in color: vec4 | |
| 63 | - | out out_color: vec4 | |
| 64 | - | do | |
| 65 | - | out_color = color | |
| 78 | + | struct FragmentOutput | |
| 79 | + | color: vec4 | |
| 66 | 80 | end | |
| 81 | + | ||
| 82 | + | @entry(fragment) | |
| 83 | + | function main(@location(0) color: vec4) -> FragmentOutput | |
| 84 | + | return FragmentOutput { | |
| 85 | + | color = color, | |
| 86 | + | } | |
| 87 | + | end | |
| 67 | 88 | ` | |
| 68 | 89 | ||
| 69 | 90 | BLUE_FRAGMENT_SHADER :: ` | |
| 70 | - | fragment main | |
| 71 | - | out color: vec4 | |
| 72 | - | do | |
| 73 | - | color = vec4(64.0 / 255.0, 64.0 / 255.0, 240.0 / 255.0, 1.0) | |
| 91 | + | struct FragmentOutput | |
| 92 | + | color: vec4 | |
| 74 | 93 | end | |
| 94 | + | ||
| 95 | + | @entry(fragment) | |
| 96 | + | function main() -> FragmentOutput | |
| 97 | + | return FragmentOutput { | |
| 98 | + | color = vec4(64.0 / 255.0, 64.0 / 255.0, 240.0 / 255.0, 1.0), | |
| 99 | + | } | |
| 100 | + | end | |
| 75 | 101 | ` | |
| 76 | 102 | ||
| 77 | 103 | SAMPLE_FRAGMENT_SHADER :: ` | |
| 104 | + | struct FragmentOutput | |
| 105 | + | color: vec4 | |
| 106 | + | end | |
| 107 | + | ||
| 78 | 108 | group frame = 0 | |
| 79 | 109 | @binding(0) texture offscreen_color: sampler2D | |
| 80 | 110 | end | |
| 81 | 111 | ||
| 82 | - | fragment main | |
| 83 | - | in uv: vec2 | |
| 84 | - | out color: vec4 | |
| 85 | - | do | |
| 86 | - | color = sample(offscreen_color, uv) | |
| 112 | + | @entry(fragment) | |
| 113 | + | function main(@location(0) uv: vec2) -> FragmentOutput | |
| 114 | + | return FragmentOutput { | |
| 115 | + | color = sample(offscreen_color, uv), | |
| 116 | + | } | |
| 87 | 117 | end | |
| 88 | 118 | ` | |
| 89 | 119 | ||
| … | 80 unchanged lines hidden | ||
| 170 | 200 | offscreen_state := ir.default_graphics_pipeline_state() | |
| 171 | 201 | offscreen_state.vertex_shader = vs | |
| 172 | 202 | offscreen_state.fragment_shader = blue_fs | |
| 203 | + | offscreen_state.color_format = .R8G8B8A8_UNORM | |
| 204 | + | offscreen_state.cull_mode = .None | |
| 173 | 205 | offscreen_pipeline := ir.add_graphics_pipeline(&frame, "offscreen-blue", offscreen_state) | |
| 174 | 206 | _ = ir.add_draw_packet(&frame, offscreen_pass, offscreen_pipeline, .Vertex_Stream, { | |
| 175 | 207 | target = offscreen_target, | |
| … | 13 unchanged lines hidden | ||
| 189 | 221 | red_state := ir.default_graphics_pipeline_state() | |
| 190 | 222 | red_state.vertex_shader = vs | |
| 191 | 223 | red_state.fragment_shader = red_fs | |
| 224 | + | red_state.color_format = .R8G8B8A8_UNORM | |
| 225 | + | red_state.cull_mode = .None | |
| 192 | 226 | red_pipeline := ir.add_graphics_pipeline(&frame, "mrt-red", red_state) | |
| 193 | 227 | _ = ir.add_draw_packet(&frame, pass, red_pipeline, .Vertex_Stream, { | |
| 194 | 228 | target = target, | |
| … | 6 unchanged lines hidden | ||
| 201 | 235 | instance_state := ir.default_graphics_pipeline_state() | |
| 202 | 236 | instance_state.vertex_shader = instance_vs | |
| 203 | 237 | instance_state.fragment_shader = instance_fs | |
| 238 | + | instance_state.color_format = .R8G8B8A8_UNORM | |
| 239 | + | instance_state.cull_mode = .None | |
| 204 | 240 | instance_state.has_instance_layout = true | |
| 205 | 241 | instance_state.instance_layout_variant = .PNUC | |
| 206 | 242 | instance_pipeline := ir.add_graphics_pipeline(&frame, "indirect-instance", instance_state) | |
| … | 18 unchanged lines hidden | ||
| 225 | 261 | sample_state := ir.default_graphics_pipeline_state() | |
| 226 | 262 | sample_state.vertex_shader = vs | |
| 227 | 263 | sample_state.fragment_shader = sample_fs | |
| 264 | + | sample_state.color_format = .R8G8B8A8_UNORM | |
| 265 | + | sample_state.cull_mode = .None | |
| 228 | 266 | sample_pipeline_desc := ir.Pipeline_Desc { | |
| 229 | 267 | kind = .Graphics, | |
| 230 | 268 | name = "sample-offscreen", | |
| … | 13 unchanged lines hidden | ||
| 244 | 282 | } | |
| 245 | 283 | ||
| 246 | 284 | main :: proc() { | |
| 247 | - | state, ok := app.init_window(i32(WIDTH), i32(HEIGHT), "GPU parity backend features") | |
| 285 | + | runtime, ok := proof.init_runtime(WIDTH, HEIGHT, "GPU parity backend features") | |
| 248 | 286 | if !ok { | |
| 249 | - | fail("parity_backend_features: failed to initialize window/backend") | |
| 287 | + | fail("parity_backend_features: failed to initialize proof runtime") | |
| 250 | 288 | } | |
| 251 | - | defer app.shutdown(&state) | |
| 289 | + | defer proof.shutdown_runtime(&runtime) | |
| 252 | 290 | ||
| 253 | 291 | backend := gpu.get_backend() | |
| 254 | 292 | if backend == nil || backend.read_texture_rgba8 == nil { | |
| … | 54 unchanged lines hidden | ||
| 1 | + | package parity_common | |
| 2 | + | ||
| 3 | + | import "core:os" | |
| 4 | + | ||
| 5 | + | import app "../../app" | |
| 6 | + | import gpu "../.." | |
| 7 | + | ||
| 8 | + | Runtime :: struct { | |
| 9 | + | app_state: app.State, | |
| 10 | + | headless: bool, | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | init_runtime :: proc(width, height: u32, title: cstring) -> (runtime: Runtime, ok: bool) { | |
| 14 | + | runtime.headless = os.get_env("GPU_PROOF_HEADLESS", context.allocator) == "1" | |
| 15 | + | if runtime.headless { | |
| 16 | + | if !gpu.init_headless(width, height, title) { | |
| 17 | + | return runtime, false | |
| 18 | + | } | |
| 19 | + | return runtime, true | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | state, app_ok := app.init_window(i32(width), i32(height), title) | |
| 23 | + | if !app_ok { | |
| 24 | + | return runtime, false | |
| 25 | + | } | |
| 26 | + | runtime.app_state = state | |
| 27 | + | return runtime, true | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | shutdown_runtime :: proc(runtime: ^Runtime) { | |
| 31 | + | if runtime == nil do return | |
| 32 | + | if runtime.headless { | |
| 33 | + | gpu.shutdown() | |
| 34 | + | return | |
| 35 | + | } | |
| 36 | + | app.shutdown(&runtime.app_state) | |
| 37 | + | } |
| … | 3 unchanged lines hidden | ||
| 4 | 4 | import "core:mem" | |
| 5 | 5 | import "core:os" | |
| 6 | 6 | ||
| 7 | - | import app "../../app" | |
| 8 | 7 | import bk "../../backend" | |
| 9 | 8 | import compiler "../../compiler" | |
| 10 | 9 | import gpu "../.." | |
| 10 | + | import proof "../parity_common" | |
| 11 | 11 | import ir "../../render_ir" | |
| 12 | 12 | import resource "../../resource" | |
| 13 | 13 | ||
| … | 1 unchanged lines hidden | ||
| 15 | 15 | HEIGHT :: u32(256) | |
| 16 | 16 | ||
| 17 | 17 | COMPUTE_SHADER :: ` | |
| 18 | - | struct Vertex | |
| 19 | - | position: vec3 | |
| 20 | - | normal: vec3 | |
| 21 | - | uv: vec2 | |
| 22 | - | end | |
| 23 | - | ||
| 24 | 18 | struct Vertices | |
| 25 | - | data: [6]Vertex | |
| 19 | + | data: [48]float | |
| 26 | 20 | end | |
| 27 | 21 | ||
| 28 | 22 | @group(0) @binding(0) | |
| … | 3 unchanged lines hidden | ||
| 32 | 26 | @workgroup_size(1, 1, 1) | |
| 33 | 27 | function main(@builtin(global_invocation_id) gid: uvec3) | |
| 34 | 28 | let z = 0.0 | |
| 35 | - | vertices.data[0].position = vec3(-1.0, -1.0, z) | |
| 36 | - | vertices.data[1].position = vec3( 1.0, -1.0, z) | |
| 37 | - | vertices.data[2].position = vec3( 1.0, 1.0, z) | |
| 38 | - | vertices.data[3].position = vec3(-1.0, -1.0, z) | |
| 39 | - | vertices.data[4].position = vec3( 1.0, 1.0, z) | |
| 40 | - | vertices.data[5].position = vec3(-1.0, 1.0, z) | |
| 41 | - | for i in 0..6 | |
| 42 | - | vertices.data[i].normal = vec3(0.0, 0.0, 1.0) | |
| 43 | - | vertices.data[i].uv = vec2(0.0, 0.0) | |
| 44 | - | end | |
| 29 | + | vertices.data[0] = -1.0 | |
| 30 | + | vertices.data[1] = -1.0 | |
| 31 | + | vertices.data[2] = z | |
| 32 | + | vertices.data[3] = 0.0 | |
| 33 | + | vertices.data[4] = 0.0 | |
| 34 | + | vertices.data[5] = 1.0 | |
| 35 | + | vertices.data[6] = 0.0 | |
| 36 | + | vertices.data[7] = 1.0 | |
| 37 | + | vertices.data[8] = 0.0 | |
| 38 | + | vertices.data[9] = -1.0 | |
| 39 | + | vertices.data[10] = z | |
| 40 | + | vertices.data[11] = 0.0 | |
| 41 | + | vertices.data[12] = 0.0 | |
| 42 | + | vertices.data[13] = 1.0 | |
| 43 | + | vertices.data[14] = 1.0 | |
| 44 | + | vertices.data[15] = 1.0 | |
| 45 | + | vertices.data[16] = 0.0 | |
| 46 | + | vertices.data[17] = 1.0 | |
| 47 | + | vertices.data[18] = z | |
| 48 | + | vertices.data[19] = 0.0 | |
| 49 | + | vertices.data[20] = 0.0 | |
| 50 | + | vertices.data[21] = 1.0 | |
| 51 | + | vertices.data[22] = 1.0 | |
| 52 | + | vertices.data[23] = 0.0 | |
| 53 | + | vertices.data[24] = -1.0 | |
| 54 | + | vertices.data[25] = -1.0 | |
| 55 | + | vertices.data[26] = z | |
| 56 | + | vertices.data[27] = 0.0 | |
| 57 | + | vertices.data[28] = 0.0 | |
| 58 | + | vertices.data[29] = 1.0 | |
| 59 | + | vertices.data[30] = 0.0 | |
| 60 | + | vertices.data[31] = 1.0 | |
| 61 | + | vertices.data[32] = 0.0 | |
| 62 | + | vertices.data[33] = 1.0 | |
| 63 | + | vertices.data[34] = z | |
| 64 | + | vertices.data[35] = 0.0 | |
| 65 | + | vertices.data[36] = 0.0 | |
| 66 | + | vertices.data[37] = 1.0 | |
| 67 | + | vertices.data[38] = 1.0 | |
| 68 | + | vertices.data[39] = 0.0 | |
| 69 | + | vertices.data[40] = -1.0 | |
| 70 | + | vertices.data[41] = 1.0 | |
| 71 | + | vertices.data[42] = z | |
| 72 | + | vertices.data[43] = 0.0 | |
| 73 | + | vertices.data[44] = 0.0 | |
| 74 | + | vertices.data[45] = 1.0 | |
| 75 | + | vertices.data[46] = 0.0 | |
| 76 | + | vertices.data[47] = 0.0 | |
| 45 | 77 | end | |
| 46 | 78 | ` | |
| 47 | 79 | ||
| 48 | 80 | VERTEX_SHADER :: ` | |
| 49 | - | vertex main | |
| 50 | - | in position: vec3 @location(0) | |
| 51 | - | in normal: vec3 @location(1) | |
| 52 | - | in uv: vec2 @location(2) | |
| 53 | - | out position: vec4 @builtin(position) | |
| 54 | - | do | |
| 55 | - | position = vec4(position, 0.0, 1.0) | |
| 81 | + | @varying | |
| 82 | + | struct VertexOutput | |
| 83 | + | @builtin(position) position: vec4 | |
| 84 | + | @location(0) uv: vec2 | |
| 56 | 85 | end | |
| 86 | + | ||
| 87 | + | @entry(vertex) | |
| 88 | + | function main(@location(0) pos: vec3, @location(1) normal: vec3, @location(2) uv: vec2) -> VertexOutput | |
| 89 | + | return VertexOutput { | |
| 90 | + | position = vec4(pos, 1.0), | |
| 91 | + | uv = uv, | |
| 92 | + | } | |
| 93 | + | end | |
| 57 | 94 | ` | |
| 58 | 95 | ||
| 59 | 96 | FRAGMENT_SHADER :: ` | |
| … | 13 unchanged lines hidden | ||
| 73 | 110 | ` | |
| 74 | 111 | ||
| 75 | 112 | SAMPLE_FRAGMENT_SHADER :: ` | |
| 113 | + | struct FragmentOutput | |
| 114 | + | color: vec4 | |
| 115 | + | end | |
| 116 | + | ||
| 76 | 117 | group frame = 0 | |
| 77 | 118 | @binding(0) texture offscreen_color: sampler2D | |
| 78 | 119 | end | |
| 79 | 120 | ||
| 80 | - | fragment main | |
| 81 | - | in uv: vec2 | |
| 82 | - | out color: vec4 | |
| 83 | - | do | |
| 84 | - | color = sample(offscreen_color, uv) | |
| 121 | + | @entry(fragment) | |
| 122 | + | function main(@location(0) uv: vec2) -> FragmentOutput | |
| 123 | + | return FragmentOutput { | |
| 124 | + | color = sample(offscreen_color, uv), | |
| 125 | + | } | |
| 85 | 126 | end | |
| 86 | 127 | ` | |
| 87 | 128 | ||
| … | 109 unchanged lines hidden | ||
| 197 | 238 | offscreen_state := ir.default_graphics_pipeline_state() | |
| 198 | 239 | offscreen_state.vertex_shader = vs | |
| 199 | 240 | offscreen_state.fragment_shader = offscreen_fs | |
| 241 | + | offscreen_state.color_format = .R8G8B8A8_UNORM | |
| 242 | + | offscreen_state.cull_mode = .None | |
| 200 | 243 | offscreen_pipeline := ir.add_graphics_pipeline(&frame, "offscreen-blue", offscreen_state) | |
| 201 | 244 | _ = ir.add_draw_packet( | |
| 202 | 245 | &frame, | |
| … | 32 unchanged lines hidden | ||
| 235 | 278 | state := ir.default_graphics_pipeline_state() | |
| 236 | 279 | state.vertex_shader = vs | |
| 237 | 280 | state.fragment_shader = fs | |
| 281 | + | state.color_format = .R8G8B8A8_UNORM | |
| 282 | + | state.cull_mode = .None | |
| 238 | 283 | pipeline := ir.add_graphics_pipeline(&frame, "draw", state) | |
| 239 | 284 | _ = ir.add_draw_packet( | |
| 240 | 285 | &frame, | |
| … | 16 unchanged lines hidden | ||
| 257 | 302 | sample_state := ir.default_graphics_pipeline_state() | |
| 258 | 303 | sample_state.vertex_shader = vs | |
| 259 | 304 | sample_state.fragment_shader = sample_fs | |
| 305 | + | sample_state.color_format = .R8G8B8A8_UNORM | |
| 306 | + | sample_state.cull_mode = .None | |
| 260 | 307 | sample_pipeline_desc := ir.Pipeline_Desc { | |
| 261 | 308 | kind = .Graphics, | |
| 262 | 309 | name = "sample-offscreen", | |
| … | 19 unchanged lines hidden | ||
| 282 | 329 | } | |
| 283 | 330 | ||
| 284 | 331 | main :: proc() { | |
| 285 | - | state, ok := app.init_window(i32(WIDTH), i32(HEIGHT), "GPU parity resource sync") | |
| 332 | + | runtime, ok := proof.init_runtime(WIDTH, HEIGHT, "GPU parity resource sync") | |
| 286 | 333 | if !ok { | |
| 287 | - | fail("parity_resource_sync: failed to initialize window/backend") | |
| 334 | + | fail("parity_resource_sync: failed to initialize proof runtime") | |
| 288 | 335 | } | |
| 289 | - | defer app.shutdown(&state) | |
| 336 | + | defer proof.shutdown_runtime(&runtime) | |
| 290 | 337 | ||
| 291 | 338 | backend := gpu.get_backend() | |
| 292 | 339 | if backend == nil || backend.read_texture_rgba8 == nil { | |
| … | 39 unchanged lines hidden | ||
| … | 5 unchanged lines hidden | ||
| 6 | 6 | ||
| 7 | 7 | import bk "../../backend" | |
| 8 | 8 | import compiler "../../compiler" | |
| 9 | - | import app "../../app" | |
| 10 | 9 | import gpu "../.." | |
| 10 | + | import proof "../parity_common" | |
| 11 | 11 | import ir "../../render_ir" | |
| 12 | 12 | import resource "../../resource" | |
| 13 | 13 | ||
| … | 1 unchanged lines hidden | ||
| 15 | 15 | HEIGHT :: u32(256) | |
| 16 | 16 | ||
| 17 | 17 | VERTEX_SHADER :: ` | |
| 18 | - | vertex main | |
| 19 | - | in position: vec3 @location(0) | |
| 20 | - | in normal: vec3 @location(1) | |
| 21 | - | in uv: vec2 @location(2) | |
| 22 | - | out position: vec4 @builtin(position) | |
| 23 | - | do | |
| 24 | - | position = vec4(position.x, position.y, 0.0, 1.0) | |
| 18 | + | @varying | |
| 19 | + | struct VertexOutput | |
| 20 | + | @builtin(position) position: vec4 | |
| 25 | 21 | end | |
| 22 | + | ||
| 23 | + | @entry(vertex) | |
| 24 | + | function main(@location(0) pos: vec3, @location(1) normal: vec3, @location(2) uv: vec2) -> VertexOutput | |
| 25 | + | return VertexOutput { | |
| 26 | + | position = vec4(pos, 1.0), | |
| 27 | + | } | |
| 28 | + | end | |
| 26 | 29 | ` | |
| 27 | 30 | ||
| 28 | 31 | FRAGMENT_SHADER :: ` | |
| 29 | - | fragment main | |
| 30 | - | out color: vec4 | |
| 31 | - | do | |
| 32 | - | color = vec4(240.0 / 255.0, 48.0 / 255.0, 96.0 / 255.0, 1.0) | |
| 32 | + | struct FragmentOutput | |
| 33 | + | color: vec4 | |
| 33 | 34 | end | |
| 35 | + | ||
| 36 | + | @entry(fragment) | |
| 37 | + | function main() -> FragmentOutput | |
| 38 | + | return FragmentOutput { | |
| 39 | + | color = vec4(240.0 / 255.0, 48.0 / 255.0, 96.0 / 255.0, 1.0), | |
| 40 | + | } | |
| 41 | + | end | |
| 34 | 42 | ` | |
| 35 | 43 | ||
| 36 | 44 | quad_vertices :: proc(x0, y0, x1, y1: f32) -> [6]resource.Mesh_Vertex_PNU { | |
| … | 98 unchanged lines hidden | ||
| 135 | 143 | state := ir.default_graphics_pipeline_state() | |
| 136 | 144 | state.vertex_shader = vs | |
| 137 | 145 | state.fragment_shader = fs | |
| 146 | + | state.color_format = .R8G8B8A8_UNORM | |
| 147 | + | state.cull_mode = .None | |
| 138 | 148 | state.enable_blending = false | |
| 139 | 149 | pipeline := ir.add_graphics_pipeline(&frame, "foreground", state) | |
| 140 | 150 | mask := ir.add_mask( | |
| … | 27 unchanged lines hidden | ||
| 168 | 178 | } | |
| 169 | 179 | ||
| 170 | 180 | main :: proc() { | |
| 171 | - | state, ok := app.init_window(i32(WIDTH), i32(HEIGHT), "GPU parity stencil clip") | |
| 181 | + | runtime, ok := proof.init_runtime(WIDTH, HEIGHT, "GPU parity stencil clip") | |
| 172 | 182 | if !ok { | |
| 173 | - | fail("parity_stencil_clip: failed to initialize window/backend") | |
| 183 | + | fail("parity_stencil_clip: failed to initialize proof runtime") | |
| 174 | 184 | } | |
| 175 | - | defer app.shutdown(&state) | |
| 185 | + | defer proof.shutdown_runtime(&runtime) | |
| 176 | 186 | ||
| 177 | 187 | backend := gpu.get_backend() | |
| 178 | 188 | if backend == nil || backend.read_texture_rgba8 == nil { | |
| … | 47 unchanged lines hidden | ||
Diff hidden because this file has more than 800 lines.
| … | 38 unchanged lines hidden | ||
| 39 | 39 | done | |
| 40 | 40 | ||
| 41 | 41 | for example in examples/*; do | |
| 42 | - | if [ -d "$example" ]; then | |
| 42 | + | if [ -d "$example" ] && [ -f "$example/main.odin" ]; then | |
| 43 | 43 | echo "==> check: $example" | |
| 44 | 44 | odin check "$example" -collection:window="$WINDOW_COLLECTION" | |
| 45 | 45 | fi | |
| … | 8 unchanged lines hidden | ||
| … | 66 unchanged lines hidden | ||
| 67 | 67 | write_line(&e.w, "") | |
| 68 | 68 | } | |
| 69 | 69 | ||
| 70 | + | emitted_struct := false | |
| 71 | + | for &s in module.structs { | |
| 72 | + | if glsl_struct_is_nested_field(module, &s) { | |
| 73 | + | emit_glsl_struct_def(&e, &s) | |
| 74 | + | emitted_struct = true | |
| 75 | + | } | |
| 76 | + | } | |
| 77 | + | if emitted_struct { | |
| 78 | + | write_line(&e.w, "") | |
| 79 | + | } | |
| 80 | + | ||
| 70 | 81 | // Bindings | |
| 71 | 82 | for &b in module.bindings { | |
| 72 | 83 | emit_glsl_binding(&e, &b) | |
| … | 11 unchanged lines hidden | ||
| 84 | 95 | return writer_to_string(e.w), e.diagnostics[:] | |
| 85 | 96 | } | |
| 86 | 97 | ||
| 98 | + | @(private = "file") | |
| 99 | + | emit_glsl_struct_def :: proc(e: ^Glsl_Emitter, s: ^IR_Struct) { | |
| 100 | + | write_line(&e.w, "struct ", s.name, " {") | |
| 101 | + | indent(&e.w) | |
| 102 | + | for f in s.fields { | |
| 103 | + | base, suffix := glsl_type_and_array_suffix(f.type) | |
| 104 | + | write_line(&e.w, base, " ", f.name, suffix, ";") | |
| 105 | + | } | |
| 106 | + | dedent(&e.w) | |
| 107 | + | write_line(&e.w, "};") | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | @(private = "file") | |
| 111 | + | glsl_struct_is_nested_field :: proc(module: ^IR_Module, candidate: ^IR_Struct) -> bool { | |
| 112 | + | for &s in module.structs { | |
| 113 | + | if s.name == candidate.name { | |
| 114 | + | continue | |
| 115 | + | } | |
| 116 | + | for f in s.fields { | |
| 117 | + | if glsl_type_references_struct(f.type, candidate.name) { | |
| 118 | + | return true | |
| 119 | + | } | |
| 120 | + | } | |
| 121 | + | } | |
| 122 | + | return false | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | @(private = "file") | |
| 126 | + | glsl_type_references_struct :: proc(t: ^Resolved_Type, name: string) -> bool { | |
| 127 | + | if t == nil do return false | |
| 128 | + | switch v in t^ { | |
| 129 | + | case Type_Struct_Resolved: | |
| 130 | + | return v.name == name | |
| 131 | + | case Type_Array_Resolved: | |
| 132 | + | return glsl_type_references_struct(v.elem, name) | |
| 133 | + | case Type_Scalar, Type_Vector, Type_Matrix, Type_Sampler, Type_Void: | |
| 134 | + | return false | |
| 135 | + | } | |
| 136 | + | return false | |
| 137 | + | } | |
| 138 | + | ||
| 87 | 139 | // -- Bindings -- | |
| 88 | 140 | ||
| 89 | 141 | @(private = "file") | |
| … | 537 unchanged lines hidden | ||
| … | 239 unchanged lines hidden | ||
| 240 | 240 | _types_initialized := false | |
| 241 | 241 | ||
| 242 | 242 | init_builtin_types :: proc() { | |
| 243 | - | if _types_initialized do return | |
| 244 | 243 | _types_initialized = true | |
| 245 | 244 | TYPE_VOID = make_type(Type_Void{}) | |
| 246 | 245 | TYPE_BOOL = make_type(Type_Scalar{.Bool}) | |
| … | 22 unchanged lines hidden | ||
| … | 207 unchanged lines hidden | ||
| 208 | 208 | } | |
| 209 | 209 | if opts.proof_output_dir then env.GPU_PROOF_OUTPUT_DIR = opts.proof_output_dir end | |
| 210 | 210 | if opts.proof_readback then env.GPU_PROOF_READBACK = opts.proof_readback end | |
| 211 | + | if opts.proof_headless then env.GPU_PROOF_HEADLESS = opts.proof_headless end | |
| 211 | 212 | if opts.proof_mode then env.GPU_PROOF_MODE = opts.proof_mode end | |
| 212 | 213 | if opts.proof_backend then env.GPU_PROOF_BACKEND = opts.proof_backend end | |
Inline diff hidden to keep this page fast.