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.. 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.. 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.. 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..