Harbor

Changelog 0ec834292f0b

pin gpu linux proof closure

@sky · 1 month ago · parent 02a9743e2662
1 added 16 modified 0 deleted
gpu/backend/opengl/gl_backend.odin +2 -0 modified
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
gpu/backend/opengl/gl_context_linux.odin +51 -13 modified
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
gpu/backend/opengl/gl_ops.odin modified

Diff hidden because this file has more than 800 lines.

gpu/backend/vulkan/vk_backend.odin +54 -50 modified
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
gpu/backend/vulkan/vk_frame.odin +36 -0 modified
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
gpu/compiler/executor.odin modified

Diff hidden because this file has more than 800 lines.

gpu/core/device.odin +149 -0 modified
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 + }
gpu/examples/parity_backend_features/main.odin +80 -42 modified
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
gpu/examples/parity_common/proof_runtime.odin +37 -0 added
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 + }
gpu/examples/parity_resource_sync/main.odin +80 -33 modified
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
gpu/examples/parity_stencil_clip/main.odin +25 -15 modified
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
gpu/gpu.odin modified

Diff hidden because this file has more than 800 lines.

gpu/scripts/check.sh +1 -1 modified
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
gpu/shader/emit_glsl.odin +52 -0 modified
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
gpu/shader/types.odin +0 -1 modified
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
gpu/tools/test.lua +1 -0 modified
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
gpu/tools/visual_matrix.lua modified

Inline diff hidden to keep this page fast.