Harbor

branch main
showing the latest snapshot on main
dump.odin 23.9 KB · Plain text
gpu/render_ir/dump.odin 0644 Raw
package render_ir

import "core:fmt"
import "core:mem"
import "core:strings"

format_resource_kind :: proc(kind: Resource_Kind) -> string {
	switch kind {
	case .Buffer:  return "Buffer"
	case .Texture: return "Texture"
	case .Sampler: return "Sampler"
	}
	return "Unknown"
}

format_resource_lifetime :: proc(lifetime: Resource_Lifetime) -> string {
	switch lifetime {
	case .Persistent: return "Persistent"
	case .Transient:  return "Transient"
	case .Imported:   return "Imported"
	}
	return "Unknown"
}

format_format :: proc(format: Format) -> string {
	switch format {
	case .Undefined:          return "Undefined"
	case .R8G8B8A8_SRGB:     return "R8G8B8A8_SRGB"
	case .R8G8B8A8_UNORM:    return "R8G8B8A8_UNORM"
	case .B8G8R8A8_SRGB:     return "B8G8R8A8_SRGB"
	case .D32_SFLOAT:        return "D32_SFLOAT"
	case .D32_SFLOAT_S8_UINT: return "D32_SFLOAT_S8_UINT"
	case .D24_UNORM_S8_UINT: return "D24_UNORM_S8_UINT"
	}
	return "Unknown"
}

format_filter :: proc(filter: Filter) -> string {
	switch filter {
	case .Nearest: return "Nearest"
	case .Linear:  return "Linear"
	}
	return "Unknown"
}

format_address_mode :: proc(mode: Address_Mode) -> string {
	switch mode {
	case .Repeat:          return "Repeat"
	case .Clamp_To_Edge:   return "Clamp_To_Edge"
	case .Clamp_To_Border: return "Clamp_To_Border"
	}
	return "Unknown"
}

format_compare_op :: proc(op: Compare_Op) -> string {
	switch op {
	case .Never:         return "Never"
	case .Less:          return "Less"
	case .Less_Or_Equal: return "Less_Or_Equal"
	case .Always:        return "Always"
	}
	return "Unknown"
}

@(private)
format_flag_append :: proc(buf: ^strings.Builder, wrote: ^bool, name: string) {
	if wrote^ {
		strings.write_string(buf, "|")
	}
	strings.write_string(buf, name)
	wrote^ = true
}

// Flag format strings use context.temp_allocator and are valid for the current call.
format_buffer_usage_flags :: proc(flags: Buffer_Usage_Flags) -> string {
	buf := strings.builder_make(context.temp_allocator)
	wrote := false
	if .Vertex in flags       { format_flag_append(&buf, &wrote, "Vertex") }
	if .Index in flags        { format_flag_append(&buf, &wrote, "Index") }
	if .Uniform in flags      { format_flag_append(&buf, &wrote, "Uniform") }
	if .Storage in flags      { format_flag_append(&buf, &wrote, "Storage") }
	if .Indirect_Argument in flags { format_flag_append(&buf, &wrote, "Indirect_Argument") }
	if .Transfer_Src in flags { format_flag_append(&buf, &wrote, "Transfer_Src") }
	if .Transfer_Dst in flags { format_flag_append(&buf, &wrote, "Transfer_Dst") }
	if !wrote { return "None" }
	return strings.to_string(buf)
}

// Flag format strings use context.temp_allocator and are valid for the current call.
format_memory_property_flags :: proc(flags: Memory_Property_Flags) -> string {
	buf := strings.builder_make(context.temp_allocator)
	wrote := false
	if .Device_Local in flags { format_flag_append(&buf, &wrote, "Device_Local") }
	if .Host_Visible in flags { format_flag_append(&buf, &wrote, "Host_Visible") }
	if .Host_Coherent in flags { format_flag_append(&buf, &wrote, "Host_Coherent") }
	if !wrote { return "None" }
	return strings.to_string(buf)
}

// Flag format strings use context.temp_allocator and are valid for the current call.
format_texture_usage_flags :: proc(flags: Texture_Usage_Flags) -> string {
	buf := strings.builder_make(context.temp_allocator)
	wrote := false
	if .Sampled in flags                  { format_flag_append(&buf, &wrote, "Sampled") }
	if .Color_Attachment in flags         { format_flag_append(&buf, &wrote, "Color_Attachment") }
	if .Depth_Stencil_Attachment in flags { format_flag_append(&buf, &wrote, "Depth_Stencil_Attachment") }
	if .Transfer_Dst in flags             { format_flag_append(&buf, &wrote, "Transfer_Dst") }
	if .Transfer_Src in flags             { format_flag_append(&buf, &wrote, "Transfer_Src") }
	if !wrote { return "None" }
	return strings.to_string(buf)
}

format_pass_kind :: proc(kind: Pass_Kind) -> string {
	switch kind {
	case .Compute:  return "Compute"
	case .Render:   return "Render"
	case .Transfer: return "Transfer"
	case .Present:  return "Present"
	}
	return "Unknown"
}

format_render_target_kind :: proc(kind: Render_Target_Kind) -> string {
	switch kind {
	case .Color: return "Color"
	case .Depth: return "Depth"
	}
	return "Unknown"
}

format_attachment_load_op :: proc(op: Attachment_Load_Op) -> string {
	switch op {
	case .Load: return "Load"
	case .Clear: return "Clear"
	case .Dont_Care: return "Dont_Care"
	}
	return "Unknown"
}

format_attachment_store_op :: proc(op: Attachment_Store_Op) -> string {
	switch op {
	case .Store: return "Store"
	case .Dont_Care: return "Dont_Care"
	}
	return "Unknown"
}

format_pipeline_kind :: proc(kind: Pipeline_Kind) -> string {
	switch kind {
	case .Graphics: return "Graphics"
	case .Compute:  return "Compute"
	}
	return "Unknown"
}

format_layout_variant :: proc(variant: Layout_Variant) -> string {
	switch variant {
	case .PNU:   return "PNU"
	case .PN:    return "PN"
	case .PNUC:  return "PNUC"
	case .PNUT:  return "PNUT"
	case .PNUCT: return "PNUCT"
	}
	return "Unknown"
}

format_topology :: proc(topology: Topology) -> string {
	switch topology {
	case .Triangle_List:  return "Triangle_List"
	case .Triangle_Strip: return "Triangle_Strip"
	case .Line_List:      return "Line_List"
	case .Point_List:     return "Point_List"
	}
	return "Unknown"
}

format_cull_mode :: proc(mode: Cull_Mode) -> string {
	switch mode {
	case .None:           return "None"
	case .Front:          return "Front"
	case .Back:           return "Back"
	case .Front_And_Back: return "Front_And_Back"
	}
	return "Unknown"
}

format_front_face :: proc(face: Front_Face) -> string {
	switch face {
	case .Counter_Clockwise: return "Counter_Clockwise"
	case .Clockwise:         return "Clockwise"
	}
	return "Unknown"
}

format_blend_mode :: proc(mode: Blend_Mode) -> string {
	switch mode {
	case .Alpha:               return "Alpha"
	case .Additive:            return "Additive"
	case .Premultiplied_Alpha: return "Premultiplied_Alpha"
	}
	return "Unknown"
}

format_shader_stage :: proc(stage: Shader_Stage) -> string {
	switch stage {
	case .Vertex:   return "Vertex"
	case .Fragment: return "Fragment"
	case .Compute:  return "Compute"
	}
	return "Unknown"
}

// Flag format strings use context.temp_allocator and are valid for the current call.
format_shader_stage_flags :: proc(flags: Shader_Stage_Flags) -> string {
	buf := strings.builder_make(context.temp_allocator)
	wrote := false
	if .Vertex in flags   { format_flag_append(&buf, &wrote, "Vertex") }
	if .Fragment in flags { format_flag_append(&buf, &wrote, "Fragment") }
	if .Compute in flags  { format_flag_append(&buf, &wrote, "Compute") }
	if !wrote { return "None" }
	return strings.to_string(buf)
}

format_shader_source_kind :: proc(kind: Shader_Source_Kind) -> string {
	switch kind {
	case .Source:   return "Source"
	case .File:     return "File"
	case .Bytecode: return "Bytecode"
	}
	return "Unknown"
}

format_shader_language :: proc(language: Shader_Language) -> string {
	switch language {
	case .Luma: return "Luma"
	}
	return "Unknown"
}

format_descriptor_type :: proc(type: Descriptor_Type) -> string {
	switch type {
	case .Combined_Image_Sampler: return "Combined_Image_Sampler"
	case .Uniform_Buffer:         return "Uniform_Buffer"
	case .Storage_Buffer:         return "Storage_Buffer"
	}
	return "Unknown"
}

format_descriptor_layout_handles :: proc(desc: Pipeline_Desc) -> string {
	if desc.descriptor_layout_count == 0 {
		return "None"
	}
	buf := strings.builder_make(context.temp_allocator)
	for i in 0..<int(desc.descriptor_layout_count) {
		if i > 0 {
			strings.write_string(&buf, "|")
		}
		fmt.sbprintf(&buf, "%d", desc.descriptor_layouts[i])
	}
	return strings.to_string(buf)
}

// Pipeline cache keys are backend-independent, intra-frame structural keys.
// Shader handles are Frame_IR-local IDs; persistent caches should replace them with content keys.
format_pipeline_cache_key :: proc(desc: Pipeline_Desc, allocator: mem.Allocator = context.allocator) -> string {
	switch desc.kind {
	case .Graphics:
		g := desc.graphics
		return fmt.aprintf(
			"gfx:vs=%d;fs=%d;layouts=%s;layout=%s;instance_layout=%v/%s;topology=%s;cull=%s;front=%s;blend=%v/%s;depth=%v/%v;color=%s;depth_format=%s;pc=%d;pc_stages=%s",
			g.vertex_shader,
			g.fragment_shader,
			format_descriptor_layout_handles(desc),
			format_layout_variant(g.layout_variant),
			g.has_instance_layout,
			format_layout_variant(g.instance_layout_variant),
			format_topology(g.topology),
			format_cull_mode(g.cull_mode),
			format_front_face(g.front_face),
			g.enable_blending,
			format_blend_mode(g.blend_mode),
			g.enable_depth_test,
			g.enable_depth_write,
			format_format(g.color_format),
			format_format(g.depth_format),
			g.push_constant_size,
			format_shader_stage_flags(g.push_constant_stages),
			allocator = allocator,
		)
	case .Compute:
		c := desc.compute
		return fmt.aprintf(
			"compute:shader=%d;layouts=%s;pc=%d;pc_stages=%s",
			c.shader,
			format_descriptor_layout_handles(desc),
			c.push_constant_size,
			format_shader_stage_flags(c.push_constant_stages),
			allocator = allocator,
		)
	}
	return "unknown"
}

format_command_kind :: proc(kind: Command_Kind) -> string {
	switch kind {
	case .Draw:     return "Draw"
	case .Dispatch: return "Dispatch"
	}
	return "Unknown"
}

format_dispatch_buffers :: proc(dispatch: Dispatch_Command) -> string {
	count := min(int(dispatch.storage_binding_count), MAX_COMPUTE_BINDINGS)
	if count == 0 {
		return "None"
	}
	buf := strings.builder_make(context.temp_allocator)
	for i in 0..<count {
		if i > 0 {
			strings.write_string(&buf, "|")
		}
		binding := dispatch.storage_bindings[i]
		fmt.sbprintf(&buf, "%d:%d", binding.binding, binding.resource)
	}
	return strings.to_string(buf)
}

format_target_kind :: proc(kind: Target_Kind) -> string {
	switch kind {
	case .Present:   return "Present"
	case .Offscreen: return "Offscreen"
	}
	return "Unknown"
}

format_order_mode :: proc(mode: Order_Mode) -> string {
	switch mode {
	case .Strict:   return "Strict"
	case .Layered:  return "Layered"
	case .Sortable: return "Sortable"
	case .Depth:    return "Depth"
	}
	return "Unknown"
}

format_clip_kind :: proc(kind: Clip_Kind) -> string {
	switch kind {
	case .None: return "None"
	case .Rect: return "Rect"
	case .Mask: return "Mask"
	}
	return "Unknown"
}

format_geometry_kind :: proc(kind: Geometry_Kind) -> string {
	switch kind {
	case .None:              return "None"
	case .Raw:               return "Raw"
	case .Mesh:              return "Mesh"
	case .Indexed_Mesh:      return "Indexed_Mesh"
	case .Vertex_Stream:     return "Vertex_Stream"
	case .Quad_Stream:       return "Quad_Stream"
	case .Line_Stream:       return "Line_Stream"
	case .Point_Stream:      return "Point_Stream"
	case .Glyph_Quad_Stream: return "Glyph_Quad_Stream"
	case .Indirect:          return "Indirect"
	}
	return "Unknown"
}

format_draw_kind :: proc(kind: Draw_Kind) -> string {
	switch kind {
	case .Raw:          return "Raw"
	case .Mesh:         return "Mesh"
	case .Indexed_Mesh: return "Indexed_Mesh"
	case .Vertex_Stream: return "Vertex_Stream"
	case .Quad_Stream:  return "Quad_Stream"
	case .Line_Stream:  return "Line_Stream"
	case .Point_Stream: return "Point_Stream"
	case .Glyph_Quad_Stream: return "Glyph_Quad_Stream"
	case .Indirect:     return "Indirect"
	case .External:     return "External"
	}
	return "Unknown"
}

@(private)
dump_write_line :: proc(buf: ^strings.Builder, wrote_line: ^bool, line: string) {
	if wrote_line^ {
		strings.write_string(buf, "\n")
	}
	strings.write_string(buf, line)
	wrote_line^ = true
}

dump_frame_ir :: proc(ir: ^Frame_IR, allocator: mem.Allocator = context.allocator) -> string {
	buf := strings.builder_make(allocator)
	wrote_line := false

	dump_write_line(&buf, &wrote_line, "Frame_IR")

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("resources %d", len(ir.resources), allocator = context.temp_allocator),
	)
	for resource, i in ir.resources {
		line: string
		switch resource.kind {
		case .Buffer:
			line = fmt.aprintf(
				"  #%d %s name=\"%s\" lifetime=%s size=%d usage=%s memory=%s",
				i + 1,
				format_resource_kind(resource.kind),
				resource.name,
				format_resource_lifetime(resource.lifetime),
				resource.buffer.size,
				format_buffer_usage_flags(resource.buffer.usage),
				format_memory_property_flags(resource.buffer.memory),
				allocator = context.temp_allocator,
			)
		case .Texture:
			line = fmt.aprintf(
				"  #%d %s name=\"%s\" lifetime=%s width=%d height=%d format=%s usage=%s",
				i + 1,
				format_resource_kind(resource.kind),
				resource.name,
				format_resource_lifetime(resource.lifetime),
				resource.texture.width,
				resource.texture.height,
				format_format(resource.texture.format),
				format_texture_usage_flags(resource.texture.usage),
				allocator = context.temp_allocator,
			)
		case .Sampler:
			line = fmt.aprintf(
				"  #%d %s name=\"%s\" lifetime=%s mag=%s min=%s address=%s,%s aniso=%v compare=%v compare_op=%s mipmap=%s",
				i + 1,
				format_resource_kind(resource.kind),
				resource.name,
				format_resource_lifetime(resource.lifetime),
				format_filter(resource.sampler.mag_filter),
				format_filter(resource.sampler.min_filter),
				format_address_mode(resource.sampler.address_mode_u),
				format_address_mode(resource.sampler.address_mode_v),
				resource.sampler.enable_aniso,
				resource.sampler.enable_compare,
				format_compare_op(resource.sampler.compare_op),
				format_filter(resource.sampler.mipmap_mode),
				allocator = context.temp_allocator,
			)
		}
		dump_write_line(&buf, &wrote_line, line)
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("passes %d", len(ir.passes), allocator = context.temp_allocator),
	)
	for pass, i in ir.passes {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf(
				"  #%d %s name=\"%s\"",
				i + 1,
				format_pass_kind(pass.kind),
				pass.name,
				allocator = context.temp_allocator,
			),
		)
		for target_index in 0..<int(pass.color_target_count) {
			target := pass.color_targets[target_index]
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    color[%d] resource=%d kind=%s format=%s load=%s store=%s clear=%.6f,%.6f,%.6f,%.6f",
					target_index,
					target.resource,
					format_render_target_kind(target.kind),
					format_format(target.format),
					format_attachment_load_op(target.load_op),
					format_attachment_store_op(target.store_op),
					target.clear_color[0],
					target.clear_color[1],
					target.clear_color[2],
					target.clear_color[3],
					allocator = context.temp_allocator,
				),
			)
		}
		if pass.has_depth_target {
			target := pass.depth_target
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    depth resource=%d kind=%s format=%s load=%s store=%s clear=%.6f",
					target.resource,
					format_render_target_kind(target.kind),
					format_format(target.format),
					format_attachment_load_op(target.load_op),
					format_attachment_store_op(target.store_op),
					target.clear_depth,
					allocator = context.temp_allocator,
				),
			)
		}
		if pass.has_viewport {
			v := pass.viewport
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    viewport x=%.6f y=%.6f width=%.6f height=%.6f depth=%.6f,%.6f",
					v.x,
					v.y,
					v.width,
					v.height,
					v.min_depth,
					v.max_depth,
					allocator = context.temp_allocator,
				),
			)
		}
		if pass.has_scissor {
			s := pass.scissor
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    scissor x=%d y=%d width=%d height=%d",
					s.x,
					s.y,
					s.width,
					s.height,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	if len(ir.targets) > 0 {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf("targets %d", len(ir.targets), allocator = context.temp_allocator),
		)
		for target, i in ir.targets {
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d %s name=\"%s\" size=%dx%d pass=%d color=%s depth=%s color_resource=%d depth_resource=%d clear=%.6f,%.6f,%.6f,%.6f depth_clear=%.6f",
					i + 1,
					format_target_kind(target.kind),
					target.name,
					target.width,
					target.height,
					target.pass,
					format_format(target.color_format),
					format_format(target.depth_format),
					target.color_resource,
					target.depth_resource,
					target.clear_color[0],
					target.clear_color[1],
					target.clear_color[2],
					target.clear_color[3],
					target.clear_depth,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	if len(ir.layers) > 0 {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf("layers %d", len(ir.layers), allocator = context.temp_allocator),
		)
		for layer, i in ir.layers {
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d target=%d name=\"%s\" sort_base=%d order=%s",
					i + 1,
					layer.target,
					layer.name,
					layer.sort_base,
					format_order_mode(layer.order),
					allocator = context.temp_allocator,
				),
			)
		}
	}

	if len(ir.clips) > 0 {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf("clips %d", len(ir.clips), allocator = context.temp_allocator),
		)
		for clip, i in ir.clips {
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d target=%d kind=%s rect=%d,%d,%d,%d",
					i + 1,
					clip.target,
					format_clip_kind(clip.kind),
					clip.rect.x,
					clip.rect.y,
					clip.rect.width,
					clip.rect.height,
					allocator = context.temp_allocator,
				),
			)
		}

		if len(ir.masks) > 0 {
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf("masks %d", len(ir.masks), allocator = context.temp_allocator),
			)
			for mask, i in ir.masks {
				dump_write_line(
					&buf,
					&wrote_line,
					fmt.aprintf(
						"  #%d target=%d name=%s source=%v layout=%v coverage=%d vertices=%d indices=%d",
						i + 1,
						mask.target,
						mask.name,
						mask.source_kind,
						mask.layout_variant,
						mask.coverage_resource,
						mask.vertex_count,
						mask.index_count,
						allocator = context.temp_allocator,
					),
				)
			}
		}
	}

	if len(ir.materials) > 0 {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf("materials %d", len(ir.materials), allocator = context.temp_allocator),
		)
		for material, i in ir.materials {
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d name=\"%s\" pipeline=%d descriptor_set=%d texture_id=%d blend=%s depth=%v/%v",
					i + 1,
					material.name,
					material.pipeline,
					material.descriptor_set,
					material.texture_id,
					format_blend_mode(material.blend_mode),
					material.enable_depth_test,
					material.enable_depth_write,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("shaders %d", len(ir.shaders), allocator = context.temp_allocator),
	)
	for shader, i in ir.shaders {
		switch shader.source_kind {
		case .Source:
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d %s %s language=%s name=\"%s\" source_len=%d",
					i + 1,
					format_shader_stage(shader.stage),
					format_shader_source_kind(shader.source_kind),
					format_shader_language(shader.language),
					shader.name,
					len(shader.source),
					allocator = context.temp_allocator,
				),
			)
		case .File:
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d %s %s language=%s name=\"%s\" path=\"%s\"",
					i + 1,
					format_shader_stage(shader.stage),
					format_shader_source_kind(shader.source_kind),
					format_shader_language(shader.language),
					shader.name,
					shader.path,
					allocator = context.temp_allocator,
				),
			)
		case .Bytecode:
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d %s %s name=\"%s\"",
					i + 1,
					format_shader_stage(shader.stage),
					format_shader_source_kind(shader.source_kind),
					shader.name,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("descriptor_layouts %d", len(ir.descriptor_layouts), allocator = context.temp_allocator),
	)
	for layout, i in ir.descriptor_layouts {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf(
				"  #%d name=\"%s\" bindings=%d",
				i + 1,
				layout.name,
				layout.binding_count,
				allocator = context.temp_allocator,
			),
		)
		for b in 0..<int(layout.binding_count) {
			binding := layout.bindings[b]
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    binding=%d type=%s count=%d stages=%s",
					binding.binding,
					format_descriptor_type(binding.type),
					binding.count,
					format_shader_stage_flags(binding.stages),
					allocator = context.temp_allocator,
				),
			)
		}
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("descriptor_sets %d", len(ir.descriptor_sets), allocator = context.temp_allocator),
	)
	for set, i in ir.descriptor_sets {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf(
				"  #%d name=\"%s\" layout=%d bindings=%d",
				i + 1,
				set.name,
				set.layout,
				set.binding_count,
				allocator = context.temp_allocator,
			),
		)
		for b in 0..<int(set.binding_count) {
			binding := set.bindings[b]
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"    binding=%d type=%s resource=%d sampler=%d size=%d",
					binding.binding,
					format_descriptor_type(binding.type),
					binding.resource,
					binding.sampler,
					binding.size,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("pipelines %d", len(ir.pipelines), allocator = context.temp_allocator),
	)
	for pipeline, i in ir.pipelines {
		dump_write_line(
			&buf,
			&wrote_line,
			fmt.aprintf(
				"  #%d %s name=\"%s\" key=\"%s\"",
				i + 1,
				format_pipeline_kind(pipeline.kind),
				pipeline.name,
				format_pipeline_cache_key(pipeline, context.temp_allocator),
				allocator = context.temp_allocator,
			),
		)
	}

	dump_write_line(
		&buf,
		&wrote_line,
		fmt.aprintf("commands %d", len(ir.commands), allocator = context.temp_allocator),
	)
	for command, i in ir.commands {
		handle := Command_Handle(i + 1)
		switch command.kind {
		case .Draw:
			draw, ok := get_draw(ir, handle)
			if !ok {
				dump_write_line(
					&buf,
					&wrote_line,
					fmt.aprintf("  #%d Draw invalid", i + 1, allocator = context.temp_allocator),
				)
				continue
			}
			if draw_kind_is_packet(draw.kind) {
				packet := draw.packet
				dump_write_line(
					&buf,
					&wrote_line,
					fmt.aprintf(
						"  #%d Draw kind=%s pass=%d pipeline=%d target=%d layer=%d order=%s sort_key=%d sequence=%d clip=%d material=%d geometry=%s/%d vertices=%d indices=%d instances=%d instance_resource=%d",
						i + 1,
						format_draw_kind(draw.kind),
						draw.pass,
						draw.pipeline,
						packet.target,
						packet.layer,
						format_order_mode(packet.order),
						packet.sort_key,
						packet.sequence,
						packet.clip,
						packet.material,
						format_geometry_kind(packet.geometry.kind),
						packet.geometry.resource,
						packet.geometry.vertex_count,
						packet.geometry.index_count,
						packet.instances.count,
						packet.instances.resource,
						allocator = context.temp_allocator,
					),
				)
				continue
			}
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d Draw pass=%d pipeline=%d vertex_count=%d instance_count=%d",
					i + 1,
					draw.pass,
					draw.pipeline,
					draw.vertex_count,
					draw.instance_count,
					allocator = context.temp_allocator,
				),
			)
		case .Dispatch:
			dispatch, ok := get_dispatch(ir, handle)
			if !ok {
				dump_write_line(
					&buf,
					&wrote_line,
					fmt.aprintf("  #%d Dispatch invalid", i + 1, allocator = context.temp_allocator),
				)
				continue
			}
			dump_write_line(
				&buf,
				&wrote_line,
				fmt.aprintf(
					"  #%d Dispatch pass=%d pipeline=%d groups=%d,%d,%d shader_id=%d buffers=%s push_constants_size=%d barrier_after=%v",
					i + 1,
					dispatch.pass,
					dispatch.pipeline,
					dispatch.groups[0],
					dispatch.groups[1],
					dispatch.groups[2],
					dispatch.shader_id,
					format_dispatch_buffers(dispatch^),
					u32(len(dispatch.push_constants)),
					dispatch.barrier_after,
					allocator = context.temp_allocator,
				),
			)
		}
	}

	return strings.to_string(buf)
}