package compiler import bk "../backend" import ir "../render_ir" import shader "../shader" import "core:log" import "core:mem" import "core:os" Prepared_Resource :: struct { resource: ir.Resource_Handle, kind: ir.Resource_Kind, buffer: bk.Buffer_Handle, texture: bk.Texture_Handle, sampler: bk.Sampler_Handle, owned: bool, } Prepared_Pass :: struct { pass: ir.Pass_Handle, render_pass: bk.Render_Pass_Handle, framebuffer: bk.Framebuffer_Handle, begin_desc: bk.Render_Pass_Begin_Desc, has_viewport: bool, viewport: ir.Viewport_Desc, has_scissor: bool, scissor: ir.Scissor_Desc, owned: bool, } Prepared_Shader :: struct { shader: ir.Shader_Handle, stage: ir.Shader_Stage, module: bk.Shader_Handle, } Prepared_Descriptor_Layout :: struct { layout: ir.Descriptor_Layout_Handle, descriptor: bk.Descriptor_Handle, } Prepared_Descriptor_Set :: struct { set: ir.Descriptor_Set_Handle, descriptor: bk.Descriptor_Handle, } Prepared_Dispatch_Descriptor :: struct { command: ir.Command_Handle, layout: bk.Descriptor_Handle, descriptor: bk.Descriptor_Handle, } Prepared_Pipeline :: struct { pipeline: ir.Pipeline_Handle, pass: ir.Pass_Handle, variant: Pipeline_Variant, stencil_ref: u8, handle: bk.Pipeline_Handle, is_compute: bool, owned: bool, } Pipeline_Variant :: enum { Content, Stencil_Write, Stencil_Test, } IMPORTED_PUSH_CONSTANT_CAP :: 256 Imported_Draw_Binding :: struct { pipeline: bk.Pipeline_Handle, descriptor_sets: [ir.MAX_DESCRIPTOR_SETS]bk.Descriptor_Handle, descriptor_set_indices: [ir.MAX_DESCRIPTOR_SETS]u32, descriptor_set_count: u8, push_constant_stages: bk.Shader_Stage_Flags, push_constant_size: u32, push_constant_bytes: [IMPORTED_PUSH_CONSTANT_CAP]u8, vertex_buffer: bk.Buffer_Handle, index_buffer: bk.Buffer_Handle, vertex_count: u32, index_count: u32, instance_count: u32, first_vertex: u32, first_index: u32, first_instance: u32, indexed: bool, } Imported_Draw_Resolver :: #type proc( user_data: rawptr, frame: ^ir.Frame_IR, command: ir.Command_Handle, draw: ^ir.Draw_Command, out: ^Imported_Draw_Binding, ) -> bool Planned_Frame_Begin_Proc :: #type proc(user_data: rawptr, ctx: bk.Frame_Context) -> bool Render_Target_Cache_Key :: struct { frame_index: u32, width, height: u32, color_count: u32, color_formats: [ir.MAX_RENDER_TARGETS]ir.Format, color_usages: [ir.MAX_RENDER_TARGETS]ir.Texture_Usage_Flags, has_depth: bool, depth_format: ir.Format, depth_usage: ir.Texture_Usage_Flags, } Render_Target_Cache_Entry :: struct { key: Render_Target_Cache_Key, colors: [ir.MAX_RENDER_TARGETS]bk.Texture_Handle, depth: bk.Texture_Handle, render_pass: bk.Render_Pass_Handle, framebuffer: bk.Framebuffer_Handle, } Render_Target_Cache :: struct { backend: ^bk.Backend, entries: [dynamic]Render_Target_Cache_Entry, } Execution_State :: struct { backend: ^bk.Backend, resources: [dynamic]Prepared_Resource, passes: [dynamic]Prepared_Pass, shaders: [dynamic]Prepared_Shader, descriptor_layouts: [dynamic]Prepared_Descriptor_Layout, descriptor_sets: [dynamic]Prepared_Descriptor_Set, dispatch_descriptors: [dynamic]Prepared_Dispatch_Descriptor, pipelines: [dynamic]Prepared_Pipeline, descriptor_pool: bk.Descriptor_Handle, imported_draw_resolver: Imported_Draw_Resolver, imported_draw_user_data: rawptr, frame_begin_proc: Planned_Frame_Begin_Proc, frame_begin_user_data: rawptr, } init_execution_state :: proc( backend: ^bk.Backend, resource_cap: int = ir.DEFAULT_RESOURCE_CAP, pass_cap: int = ir.DEFAULT_PASS_CAP, allocator: mem.Allocator = context.allocator, ) -> Execution_State { return { backend = backend, resources = make([dynamic]Prepared_Resource, 0, resource_cap, allocator), passes = make([dynamic]Prepared_Pass, 0, pass_cap, allocator), shaders = make([dynamic]Prepared_Shader, 0, ir.DEFAULT_SHADER_CAP, allocator), descriptor_layouts = make( [dynamic]Prepared_Descriptor_Layout, 0, ir.DEFAULT_DESCRIPTOR_LAYOUT_CAP, allocator, ), descriptor_sets = make( [dynamic]Prepared_Descriptor_Set, 0, ir.DEFAULT_DESCRIPTOR_SET_CAP, allocator, ), dispatch_descriptors = make( [dynamic]Prepared_Dispatch_Descriptor, 0, ir.DEFAULT_COMMAND_CAP, allocator, ), pipelines = make([dynamic]Prepared_Pipeline, 0, ir.DEFAULT_PIPELINE_CAP, allocator), } } buffer_desc_to_backend :: proc(desc: ir.Buffer_Desc) -> bk.Buffer_Desc { return { size = desc.size, usage = buffer_usage_to_backend(desc.usage), memory = memory_properties_to_backend(desc.memory), } } set_imported_draw_resolver :: proc( state: ^Execution_State, resolver: Imported_Draw_Resolver, user_data: rawptr, ) { if state == nil { return } state.imported_draw_resolver = resolver state.imported_draw_user_data = user_data } set_planned_frame_begin_proc :: proc( state: ^Execution_State, callback: Planned_Frame_Begin_Proc, user_data: rawptr, ) { if state == nil { return } state.frame_begin_proc = callback state.frame_begin_user_data = user_data } destroy_prepared_shaders :: proc(state: ^Execution_State) { if state == nil || state.backend == nil { return } for i := len(state.shaders) - 1; i >= 0; i -= 1 { prepared := state.shaders[i] if prepared.module != bk.NULL_SHADER { state.backend.destroy_shader(prepared.module) } } clear(&state.shaders) } init_render_target_cache :: proc( backend: ^bk.Backend, cap: int = ir.DEFAULT_PASS_CAP, allocator: mem.Allocator = context.allocator, ) -> Render_Target_Cache { return { backend = backend, entries = make([dynamic]Render_Target_Cache_Entry, 0, cap, allocator), } } destroy_render_target_cache :: proc(cache: ^Render_Target_Cache) { if cache.backend != nil { for i := len(cache.entries) - 1; i >= 0; i -= 1 { entry := cache.entries[i] if entry.framebuffer != bk.NULL_FRAMEBUFFER { cache.backend.destroy_framebuffer(entry.framebuffer) } if entry.render_pass != bk.NULL_RENDER_PASS { cache.backend.destroy_render_pass(entry.render_pass) } if entry.depth != bk.NULL_TEXTURE { cache.backend.destroy_image(entry.depth) } for color in entry.colors { if color != bk.NULL_TEXTURE { cache.backend.destroy_image(color) } } } } delete(cache.entries) cache^ = {} } clear_execution_state_views :: proc(state: ^Execution_State) { if state == nil { return } destroy_prepared_shaders(state) destroy_prepared_pipelines(state) destroy_prepared_descriptors(state) clear(&state.resources) clear(&state.passes) } destroy_execution_state_views :: proc(state: ^Execution_State) { if state == nil { return } destroy_prepared_shaders(state) destroy_prepared_pipelines(state) destroy_prepared_descriptors(state) delete(state.resources) delete(state.passes) delete(state.shaders) delete(state.descriptor_layouts) delete(state.descriptor_sets) delete(state.dispatch_descriptors) delete(state.pipelines) state^ = {} } destroy_execution_state :: proc(state: ^Execution_State) { if state.backend != nil { for i := len(state.passes) - 1; i >= 0; i -= 1 { pass := state.passes[i] if !pass.owned { continue } if pass.framebuffer != bk.NULL_FRAMEBUFFER { state.backend.destroy_framebuffer(pass.framebuffer) } if pass.render_pass != bk.NULL_RENDER_PASS { state.backend.destroy_render_pass(pass.render_pass) } } destroy_prepared_pipelines(state) destroy_prepared_descriptors(state) for i := len(state.resources) - 1; i >= 0; i -= 1 { resource := state.resources[i] if !resource.owned { continue } if resource.sampler != bk.NULL_SAMPLER { state.backend.destroy_sampler(resource.sampler) } if resource.texture != bk.NULL_TEXTURE { state.backend.destroy_image(resource.texture) } if resource.buffer != bk.NULL_BUFFER { state.backend.destroy_buffer(resource.buffer) } } destroy_prepared_shaders(state) } delete(state.resources) delete(state.passes) delete(state.shaders) delete(state.descriptor_layouts) delete(state.descriptor_sets) delete(state.dispatch_descriptors) delete(state.pipelines) state^ = {} } destroy_prepared_pipelines :: proc(state: ^Execution_State) { if state == nil || state.backend == nil { return } for i := len(state.pipelines) - 1; i >= 0; i -= 1 { prepared := state.pipelines[i] if prepared.owned && prepared.handle != bk.NULL_PIPELINE { if prepared.is_compute { state.backend.destroy_compute_pipeline(prepared.handle) } else { state.backend.destroy_graphics_pipeline(prepared.handle) } } } clear(&state.pipelines) } destroy_prepared_descriptors :: proc(state: ^Execution_State) { if state == nil || state.backend == nil { return } if state.descriptor_pool != bk.NULL_DESCRIPTOR { state.backend.destroy_descriptor_pool(state.descriptor_pool) state.descriptor_pool = bk.NULL_DESCRIPTOR } for i := len(state.dispatch_descriptors) - 1; i >= 0; i -= 1 { prepared := state.dispatch_descriptors[i] if prepared.layout != bk.NULL_DESCRIPTOR { state.backend.destroy_descriptor_set_layout(prepared.layout) } } clear(&state.dispatch_descriptors) clear(&state.descriptor_sets) for i := len(state.descriptor_layouts) - 1; i >= 0; i -= 1 { prepared := state.descriptor_layouts[i] if prepared.descriptor != bk.NULL_DESCRIPTOR { state.backend.destroy_descriptor_set_layout(prepared.descriptor) } } clear(&state.descriptor_layouts) } prepare_shaders :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if state == nil || frame == nil || state.backend == nil { log.error("gpu/compiler: prepare_shaders requires execution state, frame, and backend") return false } destroy_prepared_shaders(state) for shader_index in 0 ..< len(frame.shaders) { handle := ir.Shader_Handle(shader_index + 1) desc := &frame.shaders[shader_index] module, ok := prepare_shader(state, desc) if !ok { log.errorf("gpu/compiler: failed to prepare shader %s", desc.name) return false } append( &state.shaders, Prepared_Shader{shader = handle, stage = desc.stage, module = module}, ) } return true } prepared_buffer :: proc( state: ^Execution_State, resource: ir.Resource_Handle, ) -> ( bk.Buffer_Handle, bool, ) { prepared, ok := find_prepared_resource(state, resource) if !ok || prepared.buffer == bk.NULL_BUFFER { return bk.NULL_BUFFER, false } return prepared.buffer, true } prepare_shader :: proc( state: ^Execution_State, desc: ^ir.Shader_Desc, ) -> ( bk.Shader_Handle, bool, ) { if desc == nil { return {}, false } switch desc.source_kind { case .Source: return prepare_shader_source(state, desc, desc.source, desc.name) case .File: data, err := os.read_entire_file(desc.path, context.allocator) if err != nil { log.errorf("gpu/compiler: failed to read shader source %s", desc.path) return {}, false } defer delete(data) return prepare_shader_source(state, desc, string(data), desc.path) case .Bytecode: log.error( "gpu/compiler: bytecode shader descriptors need a byte slice field before lowering", ) } return {}, false } prepare_shader_source :: proc( state: ^Execution_State, desc: ^ir.Shader_Desc, source, file: string, ) -> ( bk.Shader_Handle, bool, ) { if desc.language != .Luma { log.errorf("gpu/compiler: unsupported shader language for %s", desc.name) return {}, false } result := shader.compile( source, { target = shader_target(), stage = shader_stage(desc.stage), opt_level = .None, hlsl_cbuffer_push_constants = true, hlsl_push_constant_slot = 13, hlsl_omit_register_spaces = true, }, file, ) defer shader.destroy_compile_result(&result) if !result.success { for d in result.diagnostics { msg := shader.format_diagnostic_with_source(d, source) log.errorf("gpu/compiler: shader %s: %s", desc.name, msg) delete(msg) } return {}, false } module, module_ok := state.backend.create_shader_module( { stage = backend_shader_stage(desc.stage), format = bk.REQUIRED_SHADER_FORMAT, name = desc.name, entry = "main", data = result.output, }, ) if !module_ok { log.errorf("gpu/compiler: backend failed to create shader module %s", desc.name) } return module, module_ok } prepared_shader :: proc( state: ^Execution_State, shader: ir.Shader_Handle, ) -> ( bk.Shader_Handle, bool, ) { if state == nil { return {}, false } for &prepared in state.shaders { if prepared.shader == shader { return prepared.module, prepared.module != bk.NULL_SHADER } } return {}, false } shader_target :: proc() -> shader.Target { when bk.REQUIRED_SHADER_FORMAT == .SPIRV { return .SPIR_V } else when bk.REQUIRED_SHADER_FORMAT == .HLSL { return .HLSL_SM6 } else when bk.REQUIRED_SHADER_FORMAT == .GLSL { return .GLSL_450_OPENGL } else { #assert(false, "unsupported REQUIRED_SHADER_FORMAT") } } shader_stage :: proc(stage: ir.Shader_Stage) -> shader.Shader_Stage { switch stage { case .Vertex: return .Vertex case .Fragment: return .Fragment case .Compute: return .Compute } return .None } backend_shader_stage :: proc(stage: ir.Shader_Stage) -> bk.Shader_Stage { switch stage { case .Vertex: return .Vertex case .Fragment: return .Fragment case .Compute: return .Compute } return .Vertex } render_target_cache_key :: proc( frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, frame_index: u32 = 0, ) -> ( Render_Target_Cache_Key, bool, ) { return render_target_cache_key_with_capabilities( frame, pass, bk.implemented_base_capabilities(1, 0), frame_index, ) } render_target_cache_key_with_capabilities :: proc( frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, caps: bk.Capabilities, frame_index: u32 = 0, ) -> ( Render_Target_Cache_Key, bool, ) { if pass == nil || !bk.supports_color_target_count(caps, u32(pass.color_target_count)) { return {}, false } key: Render_Target_Cache_Key key.frame_index = frame_index if pass.color_target_count > 0 { key.color_count = u32(pass.color_target_count) for i in 0.. 0 || key.has_depth) } find_render_target_cache_entry :: proc( cache: ^Render_Target_Cache, key: Render_Target_Cache_Key, ) -> ( ^Render_Target_Cache_Entry, bool, ) { if cache == nil { return nil, false } for &entry in cache.entries { if entry.key == key { return &entry, true } } return nil, false } create_cached_render_target_entry :: proc( cache: ^Render_Target_Cache, frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, key: Render_Target_Cache_Key, ) -> ( ^Render_Target_Cache_Entry, bool, ) { if cache == nil || cache.backend == nil { return nil, false } entry := Render_Target_Cache_Entry { key = key, } render_pass_desc, rp_desc_ok := build_render_pass_desc_with_capabilities( frame, pass, cache.backend.capabilities, ) if !rp_desc_ok { return nil, false } render_pass, rp_ok := cache.backend.create_render_pass(render_pass_desc) if !rp_ok { log.error("gpu/compiler: failed to create cached offscreen render pass") return nil, false } entry.render_pass = render_pass if pass.color_target_count > 0 { for i in 0.. 0 { for i in 0.. bool { if state == nil || frame == nil || cache == nil || cache.backend == nil { return false } state.backend = cache.backend clear_execution_state_views(state) for pass_index in 0 ..< len(frame.passes) { pass_handle := ir.Pass_Handle(pass_index + 1) pass := &frame.passes[pass_index] if pass.kind != .Render || !pass_has_targets(pass) { continue } if !validate_pass_color_target_count( pass, cache.backend.capabilities, "cached render target pass", ) { return false } key, key_ok := render_target_cache_key_with_capabilities( frame, pass, cache.backend.capabilities, frame_index, ) if !key_ok { return false } entry, entry_ok := find_render_target_cache_entry(cache, key) if !entry_ok { entry, entry_ok = create_cached_render_target_entry(cache, frame, pass, key) if !entry_ok { return false } } if pass.color_target_count > 0 { for i in 0.. bool { if state.backend == nil { log.error("gpu/compiler: execution state has no backend") return false } for handle_index in 0 ..< len(frame.resources) { resource_handle := ir.Resource_Handle(handle_index + 1) resource := &frame.resources[handle_index] switch resource.kind { case .Buffer: if resource.lifetime == .Imported { if state.imported_draw_resolver != nil { continue } log.errorf( "gpu/compiler: imported buffer resource %d is not supported by executor yet", resource_handle, ) return false } if resource.lifetime == .Transient && resource.buffer.size == 0 && state.imported_draw_resolver != nil { continue } buffer, ok := state.backend.create_buffer(buffer_desc_to_backend(resource.buffer)) if !ok { log.errorf("gpu/compiler: failed to create buffer resource %d", resource_handle) return false } append( &state.resources, Prepared_Resource { resource = resource_handle, kind = .Buffer, buffer = buffer, owned = true, }, ) case .Texture: if !is_render_target_texture(resource) { continue } if resource.lifetime == .Imported { log.errorf( "gpu/compiler: imported render target resource %d is not supported by executor yet", resource_handle, ) return false } texture, ok := state.backend.create_image(texture_desc_to_backend(resource.texture)) if !ok { log.errorf("gpu/compiler: failed to create texture resource %d", resource_handle) return false } aspect := texture_aspect_to_backend(resource.texture.usage) if !state.backend.create_image_view( texture, format_to_backend(resource.texture.format), aspect, ) { state.backend.destroy_image(texture) log.errorf( "gpu/compiler: failed to create image view for resource %d", resource_handle, ) return false } append( &state.resources, Prepared_Resource { resource = resource_handle, kind = .Texture, texture = texture, owned = true, }, ) case .Sampler: if resource.lifetime == .Imported { log.errorf( "gpu/compiler: imported sampler resource %d is not supported by executor yet", resource_handle, ) return false } sampler, ok := state.backend.create_sampler(sampler_desc_to_backend(resource.sampler)) if !ok { log.errorf("gpu/compiler: failed to create sampler resource %d", resource_handle) return false } append( &state.resources, Prepared_Resource { resource = resource_handle, kind = .Sampler, sampler = sampler, owned = true, }, ) } } for pass_index in 0 ..< len(frame.passes) { pass_handle := ir.Pass_Handle(pass_index + 1) pass := &frame.passes[pass_index] if pass.kind != .Render || !pass_has_targets(pass) { continue } if !validate_pass_color_target_count( pass, state.backend.capabilities, "render target pass", ) { return false } render_pass_desc, rp_desc_ok := build_render_pass_desc_with_capabilities( frame, pass, state.backend.capabilities, ) if !rp_desc_ok { return false } render_pass, rp_ok := state.backend.create_render_pass(render_pass_desc) if !rp_ok { log.errorf("gpu/compiler: failed to create render pass for IR pass %d", pass_handle) return false } framebuffer_desc, fb_desc_ok := build_framebuffer_desc(state, frame, pass, render_pass) if !fb_desc_ok { state.backend.destroy_render_pass(render_pass) return false } framebuffer, fb_ok := state.backend.create_framebuffer(framebuffer_desc) if !fb_ok { state.backend.destroy_render_pass(render_pass) log.errorf("gpu/compiler: failed to create framebuffer for IR pass %d", pass_handle) return false } append( &state.passes, Prepared_Pass { pass = pass_handle, render_pass = render_pass, framebuffer = framebuffer, begin_desc = build_begin_desc(pass, render_pass, framebuffer), has_viewport = pass.has_viewport, viewport = pass.viewport, has_scissor = pass.has_scissor, scissor = pass.scissor, owned = true, }, ) } return true } begin_prepared_pass :: proc( state: ^Execution_State, ctx: bk.Frame_Context, pass: ir.Pass_Handle, ) -> bool { prepared, ok := find_prepared_pass(state, pass) if !ok { return false } state.backend.begin_render_pass(ctx, prepared.begin_desc) if prepared.has_viewport { v := prepared.viewport state.backend.set_viewport(ctx, v.x, v.y, v.width, v.height) } if prepared.has_scissor { s := prepared.scissor state.backend.set_scissor(ctx, s.x, s.y, s.width, s.height) } return true } end_prepared_pass :: proc(state: ^Execution_State, ctx: bk.Frame_Context) { state.backend.end_render_pass(ctx) } prepared_texture :: proc( state: ^Execution_State, resource: ir.Resource_Handle, ) -> ( bk.Texture_Handle, bool, ) { prepared, ok := find_prepared_resource(state, resource) if !ok || prepared.texture == bk.NULL_TEXTURE { return bk.NULL_TEXTURE, false } return prepared.texture, true } prepared_sampler :: proc( state: ^Execution_State, resource: ir.Resource_Handle, ) -> ( bk.Sampler_Handle, bool, ) { prepared, ok := find_prepared_resource(state, resource) if !ok || prepared.sampler == bk.NULL_SAMPLER { return bk.NULL_SAMPLER, false } return prepared.sampler, true } prepared_render_pass :: proc( state: ^Execution_State, pass: ir.Pass_Handle, ) -> ( bk.Render_Pass_Handle, bool, ) { prepared, ok := find_prepared_pass(state, pass) if !ok || prepared.render_pass == bk.NULL_RENDER_PASS { return bk.NULL_RENDER_PASS, false } return prepared.render_pass, true } prepared_descriptor_layout :: proc( state: ^Execution_State, layout: ir.Descriptor_Layout_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { if state == nil { return bk.NULL_DESCRIPTOR, false } for &prepared in state.descriptor_layouts { if prepared.layout == layout { return prepared.descriptor, prepared.descriptor != bk.NULL_DESCRIPTOR } } return bk.NULL_DESCRIPTOR, false } prepared_descriptor_set :: proc( state: ^Execution_State, set: ir.Descriptor_Set_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { if state == nil { return bk.NULL_DESCRIPTOR, false } for &prepared in state.descriptor_sets { if prepared.set == set { return prepared.descriptor, prepared.descriptor != bk.NULL_DESCRIPTOR } } return bk.NULL_DESCRIPTOR, false } prepared_graphics_pipeline :: proc( state: ^Execution_State, pipeline: ir.Pipeline_Handle, pass: ir.Pass_Handle, variant: Pipeline_Variant = .Content, stencil_ref: u8 = 0, ) -> ( bk.Pipeline_Handle, bool, ) { if state == nil { return bk.NULL_PIPELINE, false } for &prepared in state.pipelines { if prepared.pipeline == pipeline && prepared.pass == pass && prepared.variant == variant && prepared.stencil_ref == stencil_ref { return prepared.handle, prepared.handle != bk.NULL_PIPELINE } } return bk.NULL_PIPELINE, false } prepared_dispatch_descriptor :: proc( state: ^Execution_State, command: ir.Command_Handle, ) -> ( bk.Descriptor_Handle, bool, ) { if state == nil { return bk.NULL_DESCRIPTOR, false } for &prepared in state.dispatch_descriptors { if prepared.command == command { return prepared.descriptor, prepared.descriptor != bk.NULL_DESCRIPTOR } } return bk.NULL_DESCRIPTOR, false } prepared_compute_pipeline :: proc( state: ^Execution_State, pipeline: ir.Pipeline_Handle, ) -> ( bk.Pipeline_Handle, bool, ) { if state == nil { return bk.NULL_PIPELINE, false } for &prepared in state.pipelines { if prepared.is_compute && prepared.pipeline == pipeline { return prepared.handle, prepared.handle != bk.NULL_PIPELINE } } return bk.NULL_PIPELINE, false } compute_pipeline_binding_count :: proc( frame: ^ir.Frame_IR, pipeline: ir.Pipeline_Handle, ) -> ( u32, bool, ) { count: u32 seen := false if frame == nil { return 0, false } for &dispatch in frame.dispatch_commands { if dispatch.pipeline != pipeline { continue } if !dispatch_storage_bindings_are_compute_layout_compatible(&dispatch) { return 0, false } dispatch_count := u32(dispatch.storage_binding_count) if !seen { count = dispatch_count seen = true } else if count != dispatch_count { return 0, false } } return count, seen } prepare_compute_pipeline :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, pipeline: ir.Pipeline_Handle, ) -> ( bk.Pipeline_Handle, bool, ) { if handle, ok := prepared_compute_pipeline(state, pipeline); ok { return handle, true } if state == nil || frame == nil || state.backend == nil { return bk.NULL_PIPELINE, false } pipeline_desc, pipeline_ok := ir.get_pipeline(frame, pipeline) if !pipeline_ok || pipeline_desc.kind != .Compute { log.errorf("gpu/compiler: invalid compute pipeline %d", pipeline) return bk.NULL_PIPELINE, false } shader_handle, shader_ok := prepared_shader(state, pipeline_desc.compute.shader) if !shader_ok { log.errorf("gpu/compiler: compute pipeline %d references unprepared shader", pipeline) return bk.NULL_PIPELINE, false } binding_count, binding_count_ok := compute_pipeline_binding_count(frame, pipeline) if !binding_count_ok { log.errorf("gpu/compiler: compute pipeline %d has incompatible dispatch storage bindings", pipeline) return bk.NULL_PIPELINE, false } handle, ok := state.backend.create_compute_pipeline( shader_handle, binding_count, pipeline_desc.compute.push_constant_size, ) if !ok { log.errorf("gpu/compiler: failed to create compute pipeline %d", pipeline) return bk.NULL_PIPELINE, false } append( &state.pipelines, Prepared_Pipeline{pipeline = pipeline, pass = ir.INVALID_PASS, handle = handle, is_compute = true, owned = true}, ) return handle, true } prepare_descriptor_layouts :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if state == nil || frame == nil || state.backend == nil { return false } for i in 0 ..< len(frame.descriptor_layouts) { handle := ir.Descriptor_Layout_Handle(i + 1) desc := &frame.descriptor_layouts[i] bindings: [ir.MAX_DESCRIPTOR_BINDINGS]bk.Descriptor_Set_Layout_Binding for j in 0 ..< int(desc.binding_count) { b := desc.bindings[j] bindings[j] = { binding = b.binding, type = descriptor_type_to_backend(b.type), count = b.count, stages = shader_stages_to_backend(b.stages), } } layout, ok := state.backend.create_descriptor_set_layout( bindings[:int(desc.binding_count)], ) if !ok { log.errorf("gpu/compiler: failed to create descriptor layout %d", handle) return false } append( &state.descriptor_layouts, Prepared_Descriptor_Layout{layout = handle, descriptor = layout}, ) } return true } descriptor_pool_add_count :: proc( types: ^[3]bk.Descriptor_Type, counts: ^[3]u32, type_count: ^int, typ: bk.Descriptor_Type, count: u32, ) -> bool { found := false for j in 0 ..< type_count^ { if types[j] == typ { counts[j] += count found = true break } } if !found { if type_count^ >= len(types^) { log.error("gpu/compiler: descriptor pool type table overflow") return false } types[type_count^] = typ counts[type_count^] = count type_count^ += 1 } return true } dispatch_needs_descriptor_set :: proc(dispatch: ^ir.Dispatch_Command) -> bool { return dispatch != nil && dispatch.storage_binding_count > 0 } dispatch_storage_bindings_are_compute_layout_compatible :: proc(dispatch: ^ir.Dispatch_Command) -> bool { if dispatch == nil { return false } for i in 0 ..< int(dispatch.storage_binding_count) { b := dispatch.storage_bindings[i] if b.type != .Storage_Buffer || b.binding != u32(i) || b.resource == ir.INVALID_RESOURCE { return false } } return true } dispatch_descriptor_set_count :: proc(frame: ^ir.Frame_IR) -> u32 { if frame == nil { return 0 } count: u32 for &dispatch in frame.dispatch_commands { if dispatch_needs_descriptor_set(&dispatch) { count += 1 } } return count } prepare_descriptor_pool :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if state == nil || frame == nil || state.backend == nil { return false } dispatch_sets := dispatch_descriptor_set_count(frame) total_sets := u32(len(frame.descriptor_sets)) + dispatch_sets if state.descriptor_pool != bk.NULL_DESCRIPTOR || total_sets == 0 { return true } types: [3]bk.Descriptor_Type counts: [3]u32 type_count := 0 for &set_desc in frame.descriptor_sets { layout_desc, layout_ok := ir.get_descriptor_set_layout(frame, set_desc.layout) if !layout_ok { log.error("gpu/compiler: descriptor set references invalid layout") return false } for i in 0 ..< int(layout_desc.binding_count) { b := layout_desc.bindings[i] typ := descriptor_type_to_backend(b.type) count := max(b.count, 1) if !descriptor_pool_add_count(&types, &counts, &type_count, typ, count) { return false } } } for &dispatch in frame.dispatch_commands { if !dispatch_needs_descriptor_set(&dispatch) { continue } if !dispatch_storage_bindings_are_compute_layout_compatible(&dispatch) { log.error("gpu/compiler: dispatch storage bindings must be contiguous 0-based storage buffers") return false } if !descriptor_pool_add_count( &types, &counts, &type_count, bk.Descriptor_Type.Storage_Buffer, u32(dispatch.storage_binding_count), ) { return false } } pool, ok := state.backend.create_descriptor_pool( total_sets, types[:type_count], counts[:type_count], ) if !ok { log.error("gpu/compiler: failed to create descriptor pool") return false } state.descriptor_pool = pool return true } prepare_descriptor_sets :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if !prepare_descriptor_pool(state, frame) { return false } for i in 0 ..< len(frame.descriptor_sets) { handle := ir.Descriptor_Set_Handle(i + 1) desc := &frame.descriptor_sets[i] layout, layout_ok := prepared_descriptor_layout(state, desc.layout) if !layout_ok { log.errorf("gpu/compiler: descriptor set %d references unprepared layout", handle) return false } set, set_ok := state.backend.allocate_descriptor_set(state.descriptor_pool, layout) if !set_ok { log.errorf("gpu/compiler: failed to allocate descriptor set %d", handle) return false } for j in 0 ..< int(desc.binding_count) { b := desc.bindings[j] switch b.type { case .Combined_Image_Sampler: texture, texture_ok := prepared_texture(state, b.resource) sampler, sampler_ok := prepared_sampler(state, b.sampler) if !texture_ok || !sampler_ok { log.errorf( "gpu/compiler: descriptor set %d binding %d references unprepared image/sampler", handle, b.binding, ) return false } state.backend.update_descriptor_image( set, b.binding, texture, sampler, .Shader_Read_Only, ) case .Uniform_Buffer, .Storage_Buffer: buffer, buffer_ok := prepared_buffer(state, b.resource) if !buffer_ok { log.errorf( "gpu/compiler: descriptor set %d binding %d references unprepared buffer", handle, b.binding, ) return false } state.backend.update_descriptor_buffer(set, b.binding, buffer, b.size) } } append(&state.descriptor_sets, Prepared_Descriptor_Set{set = handle, descriptor = set}) } return true } prepare_dispatch_descriptor_sets :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if !prepare_descriptor_pool(state, frame) { return false } for command_index in 0 ..< len(frame.commands) { record := frame.commands[command_index] if record.kind != .Dispatch { continue } dispatch := &frame.dispatch_commands[record.index] if !dispatch_needs_descriptor_set(dispatch) { continue } if !dispatch_storage_bindings_are_compute_layout_compatible(dispatch) { log.error("gpu/compiler: dispatch storage bindings must match backend compute layout") return false } bindings: [ir.MAX_COMPUTE_BINDINGS]bk.Descriptor_Set_Layout_Binding for i in 0 ..< int(dispatch.storage_binding_count) { bindings[i] = { binding = u32(i), type = .Storage_Buffer, count = 1, stages = {.Compute}, } } layout, layout_ok := state.backend.create_descriptor_set_layout( bindings[:int(dispatch.storage_binding_count)], ) if !layout_ok { log.error("gpu/compiler: failed to create dispatch descriptor layout") return false } set, set_ok := state.backend.allocate_descriptor_set(state.descriptor_pool, layout) if !set_ok { state.backend.destroy_descriptor_set_layout(layout) log.error("gpu/compiler: failed to allocate dispatch descriptor set") return false } for i in 0 ..< int(dispatch.storage_binding_count) { b := dispatch.storage_bindings[i] buffer, buffer_ok := prepared_buffer(state, b.resource) if !buffer_ok { state.backend.destroy_descriptor_set_layout(layout) log.error("gpu/compiler: dispatch descriptor references unprepared storage buffer") return false } state.backend.update_descriptor_buffer(set, b.binding, buffer, b.size) } append( &state.dispatch_descriptors, Prepared_Dispatch_Descriptor { command = ir.Command_Handle(command_index + 1), layout = layout, descriptor = set, }, ) } return true } prepare_graphics_pipeline_for_pass :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, pipeline: ir.Pipeline_Handle, pass: ir.Pass_Handle, variant: Pipeline_Variant = .Content, stencil_ref: u8 = 0, ) -> ( bk.Pipeline_Handle, bool, ) { if handle, ok := prepared_graphics_pipeline(state, pipeline, pass, variant, stencil_ref); ok { return handle, true } if state == nil || frame == nil || state.backend == nil { return bk.NULL_PIPELINE, false } pipeline_desc, pipeline_ok := ir.get_pipeline(frame, pipeline) if !pipeline_ok || pipeline_desc.kind != .Graphics { log.errorf("gpu/compiler: invalid graphics pipeline %d", pipeline) return bk.NULL_PIPELINE, false } render_pass, render_pass_ok := prepared_render_pass(state, pass) if !render_pass_ok { log.errorf("gpu/compiler: pipeline %d references unprepared pass %d", pipeline, pass) return bk.NULL_PIPELINE, false } pass_desc, pass_ok := ir.get_pass(frame, pass) if !pass_ok { log.errorf("gpu/compiler: pipeline %d references invalid pass %d", pipeline, pass) return bk.NULL_PIPELINE, false } vs, vs_ok := prepared_shader(state, pipeline_desc.graphics.vertex_shader) fs, fs_ok := prepared_shader(state, pipeline_desc.graphics.fragment_shader) if !vs_ok || !fs_ok { log.errorf("gpu/compiler: pipeline %d references unprepared shaders", pipeline) return bk.NULL_PIPELINE, false } descriptor_layouts: [ir.MAX_DESCRIPTOR_SETS]bk.Descriptor_Handle for i in 0 ..< int(pipeline_desc.descriptor_layout_count) { layout, layout_ok := prepared_descriptor_layout(state, pipeline_desc.descriptor_layouts[i]) if !layout_ok { log.errorf( "gpu/compiler: pipeline %d references unprepared descriptor layout", pipeline, ) return bk.NULL_PIPELINE, false } descriptor_layouts[i] = layout } layout := bk.variant_to_layout( layout_variant_to_backend(pipeline_desc.graphics.layout_variant), ) binding, attrs, attr_count := bk.vertex_layout_to_pipeline_attrs(&layout) vertex_bindings: [2]bk.Vertex_Binding vertex_bindings[0] = binding vertex_binding_count := 1 vertex_attrs: [16]bk.Vertex_Attribute for i in 0.. len(vertex_attrs) { log.error("gpu/compiler: combined vertex and instance attributes exceed backend capacity") return bk.NULL_PIPELINE, false } for i in 0.. bk.Stencil_State { state := bk.Stencil_State { enable = true, read_mask = 0xFF, write_mask = 0xFF, reference = stencil_ref, } switch variant { case .Stencil_Write: face := bk.Stencil_Face_State { fail_op = .Replace, pass_op = .Replace, depth_fail_op = .Replace, compare_op = .Always, } state.front = face state.back = face case .Stencil_Test: state.write_mask = 0 face := bk.Stencil_Face_State { fail_op = .Keep, pass_op = .Keep, depth_fail_op = .Keep, compare_op = .Equal, } state.front = face state.back = face case .Content: state.enable = false } return state } prepare_planned_frame :: proc(state: ^Execution_State, frame: ^ir.Frame_IR) -> bool { if state == nil || frame == nil || state.backend == nil { log.error("gpu/compiler: prepare_planned_frame requires execution state, frame, and backend") return false } if !prepare_render_targets(state, frame) { log.error("gpu/compiler: planned frame render-target preparation failed") return false } if !prepare_shaders(state, frame) { log.error("gpu/compiler: planned frame shader preparation failed") return false } if !prepare_descriptor_layouts(state, frame) { log.error("gpu/compiler: planned frame descriptor-layout preparation failed") return false } if !prepare_descriptor_sets(state, frame) { log.error("gpu/compiler: planned frame descriptor-set preparation failed") return false } if !prepare_dispatch_descriptor_sets(state, frame) { log.error("gpu/compiler: planned frame dispatch-descriptor preparation failed") return false } return true } execute_prepared_planned_frame :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, clear_color: [4]f32, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { if state == nil || frame == nil || state.backend == nil { return false } ctx, ok := state.backend.begin_frame(clear_color) if !ok { return false } if state.frame_begin_proc != nil && !state.frame_begin_proc(state.frame_begin_user_data, ctx) { _ = state.backend.end_frame(ctx) return false } success := execute_prepared_planned_commands(state, frame, ctx, clear_color, diagnostics) _ = state.backend.end_frame(ctx) return success } execute_prepared_planned_commands :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, clear_color: [4]f32, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { if state == nil || frame == nil || state.backend == nil { return false } stencil_supported := bk.supports(state.backend.capabilities, .Stencil_Clips) if frame_has_mask_clips(frame) && !stencil_supported { if diagnostics != nil { ir.validate_mask_clips_supported(frame, diagnostics, stencil_supported) } return false } if diagnostics != nil && !ir.validate_resource_accesses(frame, diagnostics) { return false } plan := ir.plan_frame_commands(frame, context.temp_allocator) defer ir.command_plan_destroy(&plan) active_pass := ir.INVALID_PASS active_default := false default_pass_closed := false success := true for command in plan { if command.culled { continue } if command.record.kind == .Dispatch { if active_default || default_pass_closed { planned_command_error( diagnostics, .Invalid_Order, command.handle, "dispatch after present pass is unsupported", ) success = false break } if active_pass != ir.INVALID_PASS { state.backend.end_render_pass(ctx) active_pass = ir.INVALID_PASS } dispatch, dispatch_ok := ir.get_dispatch(frame, command.handle) if !dispatch_ok { planned_command_error( diagnostics, .Invalid_Command, command.handle, "planned dispatch command has no payload", ) success = false break } if !execute_planned_dispatch(state, frame, ctx, command.handle, dispatch, diagnostics) { success = false break } continue } if command.record.kind != .Draw { planned_command_error( diagnostics, .Unsupported, command.handle, "planned executor only supports draw and dispatch commands", ) success = false break } draw, draw_ok := ir.get_draw(frame, command.handle) if !draw_ok { planned_command_error( diagnostics, .Invalid_Command, command.handle, "planned draw command has no payload", ) success = false break } pass, pass_ok := planned_draw_pass(frame, draw) if !pass_ok { planned_command_error( diagnostics, .Invalid_Pass, command.handle, "planned draw command has no executable pass", ) success = false break } if pass != ir.INVALID_PASS && default_pass_closed { planned_command_error( diagnostics, .Invalid_Order, command.handle, "offscreen work after present pass is unsupported", ) success = false break } if !ensure_planned_pass( state, frame, ctx, pass, draw.packet.target, clear_color, &active_pass, &active_default, &default_pass_closed, ) { success = false break } if !execute_planned_draw(state, frame, ctx, command.handle, draw, pass, diagnostics) { success = false break } } if active_pass != ir.INVALID_PASS || active_default { state.backend.end_render_pass(ctx) } return success } @(private) execute_planned_dispatch :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, dispatch: ^ir.Dispatch_Command, diagnostics: ^ir.Diagnostic_List, ) -> bool { if dispatch == nil { planned_command_error( diagnostics, .Invalid_Command, handle, "planned dispatch command has no payload", ) return false } pipeline_handle, pipeline_ok := prepare_compute_pipeline(state, frame, dispatch.pipeline) if !pipeline_ok { planned_command_error( diagnostics, .Invalid_Pipeline, handle, "planned dispatch references invalid or unprepared compute pipeline", ) return false } state.backend.bind_compute_pipeline(ctx, pipeline_handle) if dispatch_needs_descriptor_set(dispatch) { set, set_ok := prepared_dispatch_descriptor(state, handle) if !set_ok { planned_command_error( diagnostics, .Invalid_Descriptor, handle, "planned dispatch references unprepared storage descriptor set", ) return false } state.backend.bind_descriptor_set(ctx, pipeline_handle, set, 0) } if len(dispatch.push_constants) > 0 { state.backend.push_constants( ctx, pipeline_handle, {.Compute}, 0, u32(len(dispatch.push_constants)), raw_data(dispatch.push_constants), ) } state.backend.dispatch_compute(ctx, dispatch.groups[0], dispatch.groups[1], dispatch.groups[2]) if dispatch.barrier_after { state.backend.compute_barrier(ctx) } return true } @(private) planned_command_error :: proc( diagnostics: ^ir.Diagnostic_List, code: ir.Diagnostic_Code, command: ir.Command_Handle, message: string, ) { if diagnostics != nil { ir.add_command_error(diagnostics, code, command, message) return } log.errorf("gpu/compiler: command %d: %s", command, message) } @(private) draw_uses_imported_resource :: proc(frame: ^ir.Frame_IR, draw: ^ir.Draw_Command) -> bool { if frame == nil || draw == nil || draw.packet.geometry.resource == ir.INVALID_RESOURCE { return false } resource, ok := ir.get_resource(frame, draw.packet.geometry.resource) return ok && resource.lifetime == .Imported } @(private) draw_uses_runtime_bound_resource :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, draw: ^ir.Draw_Command, ) -> bool { if state == nil || state.imported_draw_resolver == nil || frame == nil || draw == nil || draw.packet.geometry.resource == ir.INVALID_RESOURCE { return false } resource, ok := ir.get_resource(frame, draw.packet.geometry.resource) if !ok || resource.kind != .Buffer { return false } if resource.lifetime == .Imported { return true } return resource.lifetime == .Transient && resource.buffer.size == 0 } @(private) planned_draw_pass :: proc(frame: ^ir.Frame_IR, draw: ^ir.Draw_Command) -> (ir.Pass_Handle, bool) { if draw.pass != ir.INVALID_PASS { return draw.pass, true } target, target_ok := ir.get_target(frame, draw.packet.target) if !target_ok { return ir.INVALID_PASS, true } return target.pass, true } @(private) ensure_planned_pass :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, pass: ir.Pass_Handle, target: ir.Target_Handle, clear_color: [4]f32, active_pass: ^ir.Pass_Handle, active_default: ^bool, default_pass_closed: ^bool, ) -> bool { if pass == ir.INVALID_PASS { if active_default^ { return true } if active_pass^ != ir.INVALID_PASS { state.backend.end_render_pass(ctx) active_pass^ = ir.INVALID_PASS } state.backend.begin_default_pass(ctx, clear_color) extent := state.backend.get_extent() width := extent.width height := extent.height if target_desc, target_ok := ir.get_target(frame, target); target_ok && target_desc.width > 0 && target_desc.height > 0 { width = target_desc.width height = target_desc.height } state.backend.set_viewport(ctx, 0, 0, f32(width), f32(height)) state.backend.set_scissor(ctx, 0, 0, width, height) active_default^ = true return true } if active_pass^ == pass { return true } if active_default^ { state.backend.end_render_pass(ctx) active_default^ = false default_pass_closed^ = true } if active_pass^ != ir.INVALID_PASS { state.backend.end_render_pass(ctx) active_pass^ = ir.INVALID_PASS } if !begin_prepared_pass(state, ctx, pass) { log.errorf("gpu/compiler: failed to begin prepared pass %d", pass) return false } active_pass^ = pass return true } @(private) execute_planned_draw :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pass: ir.Pass_Handle, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { switch draw.kind { case .Raw, .External: planned_command_error( diagnostics, .Unsupported, handle, "draw kind is not supported by Phase 5a planned executor", ) return false case .Mesh, .Indexed_Mesh, .Vertex_Stream, .Quad_Stream, .Line_Stream, .Point_Stream, .Glyph_Quad_Stream, .Indirect: // handled below } pipeline := draw.pipeline descriptor_set := ir.INVALID_DESCRIPTOR_SET if material, material_ok := ir.get_material(frame, draw.packet.material); material_ok { if pipeline == ir.INVALID_PIPELINE { pipeline = material.pipeline } descriptor_set = material.descriptor_set } if draw_uses_runtime_bound_resource(state, frame, draw) { if draw.packet.instances.resource != ir.INVALID_RESOURCE || draw.packet.instances.indirect != ir.INVALID_RESOURCE { planned_command_error( diagnostics, .Unsupported, handle, "instance buffers or indirect instances are not supported by Phase 5b imported executor", ) return false } if !apply_planned_draw_scissor(state, frame, ctx, handle, draw, pass, diagnostics) { return false } return execute_imported_planned_draw(state, frame, ctx, handle, draw, diagnostics) } if pipeline == ir.INVALID_PIPELINE { planned_command_error( diagnostics, .Invalid_Pipeline, handle, "planned draw command has no graphics pipeline", ) return false } pipeline_desc, pipeline_desc_ok := ir.get_pipeline(frame, pipeline) if !pipeline_desc_ok { return false } mask_clip, mask_desc, mask_ref, has_mask := planned_draw_mask(frame, draw) if has_mask { if !validate_mask_draw_for_stencil(state, frame, handle, draw, pass, pipeline_desc, mask_clip, mask_desc, diagnostics) { return false } } pipeline_variant := Pipeline_Variant.Stencil_Test if has_mask else .Content pipeline_handle, pipeline_ok := prepare_graphics_pipeline_for_pass( state, frame, pipeline, pass, pipeline_variant, mask_ref, ) if !pipeline_ok { return false } prepared_set := bk.NULL_DESCRIPTOR needs_descriptors := pipeline_desc.descriptor_layout_count > 0 if needs_descriptors { if descriptor_set == ir.INVALID_DESCRIPTOR_SET { planned_command_error( diagnostics, .Invalid_Descriptor, handle, "planned draw needs descriptors but has no descriptor set", ) return false } set, set_ok := prepared_descriptor_set(state, descriptor_set) if !set_ok { planned_command_error( diagnostics, .Invalid_Descriptor, handle, "planned draw references unprepared descriptor set", ) return false } prepared_set = set } if has_mask { if !execute_mask_write_draw( state, frame, ctx, handle, draw, pass, pipeline, mask_desc, mask_ref, prepared_set, needs_descriptors, diagnostics, ) { return false } } state.backend.bind_graphics_pipeline(ctx, pipeline_handle) if needs_descriptors { state.backend.bind_descriptor_set(ctx, pipeline_handle, prepared_set, 0) } if !apply_planned_draw_scissor(state, frame, ctx, handle, draw, pass, diagnostics) { return false } if draw.packet.instances.resource != ir.INVALID_RESOURCE && !planned_pipeline_accepts_instance_stream(pipeline_desc) { planned_command_error( diagnostics, .Unsupported, handle, "planned draw has an instance buffer but pipeline has no instance vertex layout", ) return false } if draw.kind == .Indirect || draw.packet.instances.indirect != ir.INVALID_RESOURCE { return execute_planned_indirect_draw( state, frame, ctx, handle, draw, pipeline_desc, diagnostics, ) } geometry := draw.packet.geometry vertex_buffer, vertex_ok := prepared_buffer(state, geometry.resource) if !vertex_ok { planned_command_error( diagnostics, .Invalid_Resource, handle, "planned draw references unprepared geometry buffer", ) return false } if !bind_planned_vertex_streams(state, ctx, handle, draw, pipeline_desc, vertex_buffer, diagnostics) { return false } instance_count := draw.instance_count if instance_count == 0 { if draw.packet.instances.count > 0 { instance_count = draw.packet.instances.count } else { instance_count = 1 } } if draw.kind == .Indexed_Mesh || geometry.kind == .Indexed_Mesh { index_count := geometry.index_count if index_count == 0 { index_count = draw.vertex_count } if index_count == 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "planned indexed draw has no index count", ) return false } state.backend.bind_index_buffer(ctx, vertex_buffer) state.backend.draw_indexed( ctx, index_count, instance_count, geometry.first_index, i32(geometry.first_vertex), 0, ) return true } vertex_count := draw.vertex_count if vertex_count == 0 { vertex_count = geometry.vertex_count } if vertex_count == 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "planned draw has no vertex count", ) return false } state.backend.draw(ctx, vertex_count, instance_count, geometry.first_vertex, 0) return true } @(private) planned_draw_mask :: proc( frame: ^ir.Frame_IR, draw: ^ir.Draw_Command, ) -> ( ^ir.Clip_Desc, ^ir.Mask_Desc, u8, bool, ) { if frame == nil || draw == nil || draw.packet.clip == ir.INVALID_CLIP { return nil, nil, 0, false } clip, clip_ok := ir.get_clip(frame, draw.packet.clip) if !clip_ok || clip.kind != .Mask { return nil, nil, 0, false } mask, mask_ok := ir.get_mask(frame, clip.mask) if !mask_ok { return clip, nil, 0, true } ref := u8(clip.mask) return clip, mask, ref, true } @(private) validate_mask_draw_for_stencil :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pass: ir.Pass_Handle, pipeline_desc: ^ir.Pipeline_Desc, clip: ^ir.Clip_Desc, mask: ^ir.Mask_Desc, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { if state == nil || frame == nil || draw == nil || pipeline_desc == nil || clip == nil || mask == nil { planned_command_error(diagnostics, .Invalid_Mask, handle, "planned mask clip references invalid mask") return false } if !bk.supports(state.backend.capabilities, .Stencil_Clips) { planned_command_error(diagnostics, .Stencil_Unsupported, handle, "mask clips require backend Stencil_Clips support") return false } if int(clip.mask) > 255 { planned_command_error(diagnostics, .Unsupported, handle, "mask clips require an 8-bit stencil reference") return false } if mask.target != clip.target || clip.target != draw.packet.target { planned_command_error(diagnostics, .Invalid_Mask, handle, "mask clip target does not match draw target") return false } if mask.layout_variant != pipeline_desc.graphics.layout_variant { planned_command_error(diagnostics, .Invalid_Mask, handle, "mask coverage layout must match clipped pipeline layout") return false } pass_desc, pass_ok := ir.get_pass(frame, pass) if !pass_ok || !pass_desc.has_depth_target { planned_command_error(diagnostics, .Stencil_Unsupported, handle, "mask clips require a stencil-capable depth target") return false } depth_format := format_to_backend(pass_desc.depth_target.format) if !backend_format_has_stencil(depth_format) { planned_command_error(diagnostics, .Stencil_Unsupported, handle, "mask clips require a packed depth-stencil target") return false } return true } @(private) execute_mask_write_draw :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pass: ir.Pass_Handle, pipeline: ir.Pipeline_Handle, mask: ^ir.Mask_Desc, mask_ref: u8, prepared_set: bk.Descriptor_Handle, needs_descriptors: bool, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { mask_pipeline, mask_pipeline_ok := prepare_graphics_pipeline_for_pass( state, frame, pipeline, pass, .Stencil_Write, mask_ref, ) if !mask_pipeline_ok { return false } mask_buffer, mask_buffer_ok := prepared_buffer(state, mask.coverage_resource) if !mask_buffer_ok { planned_command_error(diagnostics, .Invalid_Resource, handle, "mask coverage references unprepared geometry buffer") return false } state.backend.bind_graphics_pipeline(ctx, mask_pipeline) if needs_descriptors { state.backend.bind_descriptor_set(ctx, mask_pipeline, prepared_set, 0) } if !apply_planned_draw_scissor(state, frame, ctx, handle, draw, pass, diagnostics) { return false } state.backend.bind_vertex_buffer(ctx, mask_buffer) if mask.index_resource != ir.INVALID_RESOURCE { index_buffer, index_ok := prepared_buffer(state, mask.index_resource) if !index_ok { planned_command_error(diagnostics, .Invalid_Resource, handle, "mask coverage references unprepared index buffer") return false } state.backend.bind_index_buffer(ctx, index_buffer) state.backend.draw_indexed(ctx, mask.index_count, 1, 0, 0, 0) return true } state.backend.draw(ctx, mask.vertex_count, 1, 0, 0) return true } @(private) execute_planned_indirect_draw :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pipeline_desc: ^ir.Pipeline_Desc, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { argument_resource := draw.packet.instances.indirect if argument_resource == ir.INVALID_RESOURCE { planned_command_error( diagnostics, .Invalid_Resource, handle, "indirect planned draw has no argument buffer", ) return false } if (draw.packet.instances.offset & 3) != 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "indirect planned draw argument offset must be 4-byte aligned", ) return false } draw_count := draw.packet.instances.count if draw_count == 0 { draw_count = 1 } if draw_count != 1 { planned_command_error( diagnostics, .Unsupported, handle, "indirect planned draw currently supports one command", ) return false } geometry := draw.packet.geometry vertex_buffer, vertex_ok := prepared_buffer(state, geometry.resource) if !vertex_ok { planned_command_error( diagnostics, .Invalid_Resource, handle, "indirect planned draw references unprepared geometry buffer", ) return false } argument_buffer, argument_ok := prepared_buffer(state, argument_resource) if !argument_ok { planned_command_error( diagnostics, .Invalid_Resource, handle, "indirect planned draw references unprepared argument buffer", ) return false } indexed := geometry.kind == .Indexed_Mesh min_stride := u32(size_of(bk.Indirect_Draw_Indexed_Args)) if indexed else u32(size_of(bk.Indirect_Draw_Args)) stride := draw.packet.instances.stride if stride == 0 { stride = min_stride } if stride < min_stride || (stride & 3) != 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "indirect planned draw stride must be 4-byte aligned and at least the argument size", ) return false } if !bind_planned_vertex_streams(state, ctx, handle, draw, pipeline_desc, vertex_buffer, diagnostics) { return false } if indexed { if state.backend.draw_indexed_indirect == nil { planned_command_error( diagnostics, .Unsupported, handle, "backend does not support indexed indirect draws", ) return false } state.backend.bind_index_buffer(ctx, vertex_buffer) state.backend.draw_indexed_indirect( ctx, argument_buffer, draw.packet.instances.offset, draw_count, stride, ) return true } if state.backend.draw_indirect == nil { planned_command_error( diagnostics, .Unsupported, handle, "backend does not support indirect draws", ) return false } state.backend.draw_indirect( ctx, argument_buffer, draw.packet.instances.offset, draw_count, stride, ) return true } @(private) planned_pipeline_accepts_instance_stream :: proc(desc: ^ir.Pipeline_Desc) -> bool { return desc != nil && desc.kind == .Graphics && desc.graphics.has_instance_layout } @(private) pipeline_vertex_stride :: proc(desc: ^ir.Pipeline_Desc) -> u32 { if desc == nil || desc.kind != .Graphics { return 0 } layout := bk.variant_to_layout(layout_variant_to_backend(desc.graphics.layout_variant)) binding, _, _ := bk.vertex_layout_to_pipeline_attrs(&layout) return binding.stride } @(private) bind_planned_vertex_streams :: proc( state: ^Execution_State, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pipeline_desc: ^ir.Pipeline_Desc, vertex_buffer: bk.Buffer_Handle, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { if draw == nil { return false } if state.backend.bind_vertex_buffer_slot == nil { planned_command_error( diagnostics, .Unsupported, handle, "backend does not support slot-aware vertex buffer binding", ) return false } state.backend.bind_vertex_buffer_slot( ctx, 0, vertex_buffer, 0, pipeline_vertex_stride(pipeline_desc), ) if draw.packet.instances.resource == ir.INVALID_RESOURCE { return true } if draw.packet.instances.stride == 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "planned instance-buffer draw requires a nonzero instance stride", ) return false } instance_buffer, instance_ok := prepared_buffer(state, draw.packet.instances.resource) if !instance_ok { planned_command_error( diagnostics, .Invalid_Resource, handle, "planned instance-buffer draw references unprepared instance buffer", ) return false } state.backend.bind_vertex_buffer_slot( ctx, 1, instance_buffer, draw.packet.instances.offset, draw.packet.instances.stride, ) return true } @(private) apply_planned_draw_scissor :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, pass: ir.Pass_Handle, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { scissor: ir.Scissor_Desc if draw.packet.clip != ir.INVALID_CLIP { scissor_ok: bool scissor, scissor_ok = ir.packet_effective_scissor(frame, draw.packet) if !scissor_ok { if clip, clip_ok := ir.get_clip(frame, draw.packet.clip); clip_ok && clip.kind == .Mask { if !bk.supports(state.backend.capabilities, .Stencil_Clips) { planned_command_error( diagnostics, .Stencil_Unsupported, handle, "mask clips require backend Stencil_Clips support", ) return false } scissor, scissor_ok = planned_draw_default_scissor( state, frame, pass, draw.packet.target, ) if scissor_ok { state.backend.set_scissor( ctx, scissor.x, scissor.y, scissor.width, scissor.height, ) return true } } planned_command_error( diagnostics, .Invalid_Command, handle, "planned draw has invalid clip", ) return false } } else { scissor_ok: bool scissor, scissor_ok = planned_draw_default_scissor(state, frame, pass, draw.packet.target) if !scissor_ok { planned_command_error( diagnostics, .Invalid_Command, handle, "planned draw has no valid scissor extent", ) return false } } state.backend.set_scissor(ctx, scissor.x, scissor.y, scissor.width, scissor.height) return true } @(private) frame_has_mask_clips :: proc(frame: ^ir.Frame_IR) -> bool { if frame == nil { return false } for clip in frame.clips { if clip.kind == .Mask { return true } } return false } @(private) planned_draw_default_scissor :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, pass: ir.Pass_Handle, target: ir.Target_Handle, ) -> (ir.Scissor_Desc, bool) { if pass != ir.INVALID_PASS { pass_desc, pass_ok := ir.get_pass(frame, pass) if !pass_ok { return {}, false } if pass_desc.has_scissor { return pass_desc.scissor, true } if pass_desc.has_viewport { return { x = i32(pass_desc.viewport.x), y = i32(pass_desc.viewport.y), width = u32(pass_desc.viewport.width), height = u32(pass_desc.viewport.height), }, true } width, height, extent_ok := planned_pass_extent(frame, pass_desc) if extent_ok { return {x = 0, y = 0, width = width, height = height}, true } extent := state.backend.get_extent() return { x = 0, y = 0, width = extent.width, height = extent.height, }, extent.width > 0 && extent.height > 0 } extent := state.backend.get_extent() width := extent.width height := extent.height if target_desc, target_ok := ir.get_target(frame, target); target_ok && target_desc.width > 0 && target_desc.height > 0 { width = target_desc.width height = target_desc.height } return {x = 0, y = 0, width = width, height = height}, width > 0 && height > 0 } @(private) planned_pass_extent :: proc( frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, ) -> (u32, u32, bool) { if pass.color_target_count > 0 { target := pass.color_targets[0] resource, ok := ir.get_resource(frame, target.resource) if ok && resource.kind == .Texture && resource.texture.width > 0 && resource.texture.height > 0 { return resource.texture.width, resource.texture.height, true } } if pass.has_depth_target { target := pass.depth_target resource, ok := ir.get_resource(frame, target.resource) if ok && resource.kind == .Texture && resource.texture.width > 0 && resource.texture.height > 0 { return resource.texture.width, resource.texture.height, true } } return 0, 0, false } @(private) execute_imported_planned_draw :: proc( state: ^Execution_State, frame: ^ir.Frame_IR, ctx: bk.Frame_Context, handle: ir.Command_Handle, draw: ^ir.Draw_Command, diagnostics: ^ir.Diagnostic_List = nil, ) -> bool { if state.imported_draw_resolver == nil { planned_command_error( diagnostics, .Unsupported, handle, "planned draw references imported geometry without an imported resource resolver", ) return false } binding: Imported_Draw_Binding if !state.imported_draw_resolver( state.imported_draw_user_data, frame, handle, draw, &binding, ) { planned_command_error( diagnostics, .Invalid_Resource, handle, "imported draw resolver failed", ) return false } if binding.pipeline == bk.NULL_PIPELINE || binding.vertex_buffer == bk.NULL_BUFFER { planned_command_error( diagnostics, .Invalid_Resource, handle, "imported draw resolver returned incomplete backend handles", ) return false } if binding.instance_count == 0 { binding.instance_count = 1 } state.backend.bind_graphics_pipeline(ctx, binding.pipeline) for i in 0 ..< int(binding.descriptor_set_count) { set := binding.descriptor_sets[i] if set != bk.NULL_DESCRIPTOR { state.backend.bind_descriptor_set( ctx, binding.pipeline, set, binding.descriptor_set_indices[i], ) } } if binding.push_constant_size > 0 { if binding.push_constant_size > IMPORTED_PUSH_CONSTANT_CAP { planned_command_error( diagnostics, .Invalid_Command, handle, "imported draw push constants exceed executor capacity", ) return false } state.backend.push_constants( ctx, binding.pipeline, binding.push_constant_stages, 0, binding.push_constant_size, &binding.push_constant_bytes[0], ) } state.backend.bind_vertex_buffer(ctx, binding.vertex_buffer) if binding.indexed { if binding.index_buffer == bk.NULL_BUFFER || binding.index_count == 0 { planned_command_error( diagnostics, .Invalid_Resource, handle, "imported indexed draw missing index buffer or count", ) return false } state.backend.bind_index_buffer(ctx, binding.index_buffer) state.backend.draw_indexed( ctx, binding.index_count, binding.instance_count, binding.first_index, i32(binding.first_vertex), binding.first_instance, ) return true } if binding.vertex_count == 0 { planned_command_error( diagnostics, .Invalid_Command, handle, "imported draw missing vertex count", ) return false } state.backend.draw( ctx, binding.vertex_count, binding.instance_count, binding.first_vertex, binding.first_instance, ) return true } materialize_frame_targets :: proc(frame: ^ir.Frame_IR) -> bool { if frame == nil { return false } for target_index in 0 ..< len(frame.targets) { target := &frame.targets[target_index] if target.pass != ir.INVALID_PASS { continue } if target.kind == .Present { continue } if target.kind != .Offscreen { return false } if target.width == 0 || target.height == 0 { return false } pass := ir.Pass_Desc { kind = .Render, name = target.name, } if target.color_format != .Undefined { if target.color_resource == ir.INVALID_RESOURCE { target.color_resource = ir.add_texture( frame, "target-color", target.width, target.height, target.color_format, {.Color_Attachment, .Sampled}, .Transient, ) } if !ir.pass_add_color_target( &pass, ir.make_color_target( target.color_resource, target.color_format, .Clear, .Store, target.clear_color, ), ) { return false } } if target.depth_format != .Undefined { if target.depth_resource == ir.INVALID_RESOURCE { target.depth_resource = ir.add_texture( frame, "target-depth", target.width, target.height, target.depth_format, {.Depth_Stencil_Attachment}, .Transient, ) } ir.pass_set_depth_target( &pass, ir.make_depth_target( target.depth_resource, target.depth_format, .Clear, .Store, target.clear_depth, ), ) } if pass.color_target_count == 0 && !pass.has_depth_target { return false } ir.pass_set_viewport( &pass, { x = 0, y = 0, width = f32(target.width), height = f32(target.height), min_depth = 0, max_depth = 1, }, ) ir.pass_set_scissor(&pass, {x = 0, y = 0, width = target.width, height = target.height}) target.pass = ir.add_pass(frame, pass) } return true } build_render_pass_desc :: proc( frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, ) -> ( bk.Render_Pass_Desc, bool, ) { return build_render_pass_desc_with_capabilities( frame, pass, bk.implemented_base_capabilities(1, 0), ) } build_render_pass_desc_with_capabilities :: proc( frame: ^ir.Frame_IR, pass: ^ir.Pass_Desc, caps: bk.Capabilities, ) -> ( bk.Render_Pass_Desc, bool, ) { if pass == nil || !bk.supports_color_target_count(caps, u32(pass.color_target_count)) { return {}, false } desc: bk.Render_Pass_Desc if pass.color_target_count > 0 { desc.has_color = true desc.color_count = u32(pass.color_target_count) for i in 0.. bool { switch format { case .D32_SFLOAT_S8_UINT, .D24_UNORM_S8_UINT: return true case .Undefined, .R8G8B8A8_SRGB, .R8G8B8A8_UNORM, .B8G8R8A8_SRGB, .D32_SFLOAT: return false } return false } build_begin_desc :: proc( pass: ^ir.Pass_Desc, render_pass: bk.Render_Pass_Handle, framebuffer: bk.Framebuffer_Handle, ) -> bk.Render_Pass_Begin_Desc { desc := bk.Render_Pass_Begin_Desc { pass = render_pass, framebuffer = framebuffer, clear_depth = 1, } if pass.color_target_count > 0 { desc.color_count = u32(pass.color_target_count) for i in 0.. ( bk.Framebuffer_Desc, bool, ) { desc := bk.Framebuffer_Desc { pass = render_pass, layers = 1, } if pass.color_target_count > 0 { desc.color_count = u32(pass.color_target_count) for i in 0.. bk.Format { 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 .Undefined } texture_usage_to_backend :: proc(usage: ir.Texture_Usage_Flags) -> bk.Image_Usage_Flags { result: bk.Image_Usage_Flags if .Sampled in usage {result += {.Sampled}} if .Color_Attachment in usage {result += {.Color_Attachment}} if .Depth_Stencil_Attachment in usage {result += {.Depth_Stencil_Attachment}} if .Transfer_Dst in usage {result += {.Transfer_Dst}} if .Transfer_Src in usage {result += {.Transfer_Src}} return result } buffer_usage_to_backend :: proc(usage: ir.Buffer_Usage_Flags) -> bk.Buffer_Usage_Flags { result: bk.Buffer_Usage_Flags if .Vertex in usage {result += {.Vertex}} if .Index in usage {result += {.Index}} if .Uniform in usage {result += {.Uniform}} if .Storage in usage {result += {.Storage}} if .Indirect_Argument in usage {result += {.Indirect_Argument}} if .Transfer_Src in usage {result += {.Transfer_Src}} if .Transfer_Dst in usage {result += {.Transfer_Dst}} return result } memory_properties_to_backend :: proc( memory: ir.Memory_Property_Flags, ) -> bk.Memory_Property_Flags { result: bk.Memory_Property_Flags if .Device_Local in memory {result += {.Device_Local}} if .Host_Visible in memory {result += {.Host_Visible}} if .Host_Coherent in memory {result += {.Host_Coherent}} return result } descriptor_type_to_backend :: proc(typ: ir.Descriptor_Type) -> bk.Descriptor_Type { switch typ { case .Combined_Image_Sampler: return .Combined_Image_Sampler case .Uniform_Buffer: return .Uniform_Buffer case .Storage_Buffer: return .Storage_Buffer } return .Uniform_Buffer } shader_stages_to_backend :: proc(stages: ir.Shader_Stage_Flags) -> bk.Shader_Stage_Flags { result: bk.Shader_Stage_Flags if .Vertex in stages {result += {.Vertex}} if .Fragment in stages {result += {.Fragment}} if .Compute in stages {result += {.Compute}} return result } layout_variant_to_backend :: proc(variant: ir.Layout_Variant) -> bk.Layout_Variant { switch variant { case .PNU: return .PNU case .PN: return .PN case .PNUC: return .PNUC case .PNUT: return .PNUT case .PNUCT: return .PNUCT } return .PNU } topology_to_backend :: proc(topology: ir.Topology) -> bk.Topology { 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 .Triangle_List } cull_mode_to_backend :: proc(mode: ir.Cull_Mode) -> bk.Cull_Mode { switch mode { case .None: return .None case .Front: return .Front case .Back: return .Back case .Front_And_Back: return .Front_And_Back } return .None } front_face_to_backend :: proc(face: ir.Front_Face) -> bk.Front_Face { switch face { case .Counter_Clockwise: return .Counter_Clockwise case .Clockwise: return .Clockwise } return .Counter_Clockwise } blend_mode_to_backend :: proc(mode: ir.Blend_Mode) -> bk.Blend_Mode { switch mode { case .Alpha: return .Alpha case .Additive: return .Additive case .Premultiplied_Alpha: return .Premultiplied_Alpha } return .Alpha } load_op_to_backend :: proc(op: ir.Attachment_Load_Op) -> bk.Attachment_Load_Op { switch op { case .Load: return .Load case .Clear: return .Clear case .Dont_Care: return .Dont_Care } return .Load } store_op_to_backend :: proc(op: ir.Attachment_Store_Op) -> bk.Attachment_Store_Op { switch op { case .Store: return .Store case .Dont_Care: return .Dont_Care } return .Store } sampler_desc_to_backend :: proc(desc: ir.Sampler_Desc) -> bk.Sampler_Desc { return { mag_filter = filter_to_backend(desc.mag_filter), min_filter = filter_to_backend(desc.min_filter), address_mode_u = address_mode_to_backend(desc.address_mode_u), address_mode_v = address_mode_to_backend(desc.address_mode_v), enable_aniso = desc.enable_aniso, enable_compare = desc.enable_compare, compare_op = compare_op_to_backend(desc.compare_op), mipmap_mode = filter_to_backend(desc.mipmap_mode), } } texture_desc_to_backend :: proc(desc: ir.Texture_Desc) -> bk.Texture_Desc { return { width = desc.width, height = desc.height, format = format_to_backend(desc.format), usage = texture_usage_to_backend(desc.usage), } } @(private) filter_to_backend :: proc(filter: ir.Filter) -> bk.Filter { switch filter { case .Nearest: return .Nearest case .Linear: return .Linear } return .Linear } @(private) address_mode_to_backend :: proc(mode: ir.Address_Mode) -> bk.Address_Mode { switch mode { case .Repeat: return .Repeat case .Clamp_To_Edge: return .Clamp_To_Edge case .Clamp_To_Border: return .Clamp_To_Border } return .Repeat } @(private) compare_op_to_backend :: proc(op: ir.Compare_Op) -> bk.Compare_Op { switch op { case .Never: return .Never case .Less: return .Less case .Less_Or_Equal: return .Less_Or_Equal case .Always: return .Always } return .Always } @(private) is_render_target_texture :: proc(resource: ^ir.Resource_Desc) -> bool { usage := resource.texture.usage return .Color_Attachment in usage || .Depth_Stencil_Attachment in usage } @(private) texture_aspect_to_backend :: proc(usage: ir.Texture_Usage_Flags) -> bk.Image_Aspect_Flags { if .Depth_Stencil_Attachment in usage { return {.Depth} } return {.Color} } @(private) pass_has_targets :: proc(pass: ^ir.Pass_Desc) -> bool { return pass.color_target_count > 0 || pass.has_depth_target } @(private) validate_pass_color_target_count :: proc( pass: ^ir.Pass_Desc, caps: bk.Capabilities, label: string, ) -> bool { if pass == nil { return false } if bk.supports_color_target_count(caps, u32(pass.color_target_count)) { return true } log.errorf( "gpu/compiler: %s requires %d color targets; backend implemented limit is %d", label, u32(pass.color_target_count), caps.max_color_targets, ) return false } @(private) find_prepared_resource :: proc( state: ^Execution_State, resource: ir.Resource_Handle, ) -> ( ^Prepared_Resource, bool, ) { for &prepared in state.resources { if prepared.resource == resource { return &prepared, true } } return nil, false } @(private) find_prepared_pass :: proc( state: ^Execution_State, pass: ir.Pass_Handle, ) -> ( ^Prepared_Pass, bool, ) { for &prepared in state.passes { if prepared.pass == pass { return &prepared, true } } return nil, false }