package gpu import bk "backend" import "compiler" import "core:log" import "core:mem" import ir "render_ir" import "renderer" import "resource" Frame_IR :: ir.Frame_IR Diagnostic :: ir.Diagnostic Diagnostic_List :: ir.Diagnostic_List Target_Handle :: ir.Target_Handle Pass_Handle :: ir.Pass_Handle Layer_Handle :: ir.Layer_Handle Clip_Handle :: ir.Clip_Handle Mask_Handle :: ir.Mask_Handle Material_Handle :: ir.Material_Handle Resource_Handle :: ir.Resource_Handle Command_Handle :: ir.Command_Handle Draw_Kind :: ir.Draw_Kind Order_Mode :: ir.Order_Mode Target_Desc :: ir.Target_Desc Layer_Desc :: ir.Layer_Desc Clip_Desc :: ir.Clip_Desc Mask_Desc :: ir.Mask_Desc Material_Desc :: ir.Material_Desc Draw_Packet :: ir.Draw_Packet Geometry_View :: ir.Geometry_View Geometry_Kind :: ir.Geometry_Kind Instance_View :: ir.Instance_View Draw_Flags :: ir.Draw_Flags Bounds3 :: ir.Bounds3 Planned_Command :: ir.Planned_Command INVALID_TARGET :: ir.INVALID_TARGET INVALID_PASS :: ir.INVALID_PASS INVALID_LAYER :: ir.INVALID_LAYER INVALID_CLIP :: ir.INVALID_CLIP INVALID_MASK :: ir.INVALID_MASK INVALID_MATERIAL :: ir.INVALID_MATERIAL INVALID_RESOURCE :: ir.INVALID_RESOURCE INVALID_COMMAND :: ir.INVALID_COMMAND MAX_FRAME_LAYER_STACK :: 32 Context :: struct { default_width: u32, default_height: u32, color_format: ir.Format, depth_format: ir.Format, runtime: ^Engine_State, owns_runtime: bool, } Imported_Mesh_Geometry :: struct { resource: Resource_Handle, mesh: Mesh, } Imported_Storage_Buffer :: struct { resource: Resource_Handle, buffer: Storage_Buffer, } Storage_Buffer_View :: struct { resource: Resource_Handle, size: u64, } Storage_Buffer_Binding :: struct { binding: u32, buffer: Storage_Buffer_View, } Raw_Frame_Draw_Proc :: #type proc( user_data: rawptr, backend: ^bk.Backend, ctx: bk.Frame_Context, ) -> bool Generated_Raw_Draw :: struct { resource: Resource_Handle, callback: Raw_Frame_Draw_Proc, user_data: rawptr, } Generated_Rect_Geometry :: struct { resource: Resource_Handle, x, y: f32, width: f32, height: f32, color: Color, } Generated_Textured_Quad_Geometry :: struct { resource: Resource_Handle, texture_id: u32, sampled_resource: Resource_Handle, x, y: f32, width: f32, height: f32, u0, v0: f32, u1, v1: f32, rotation: f32, color: Color, } Generated_Line_Geometry :: struct { resource: Resource_Handle, x0, y0: f32, x1, y1: f32, thickness: f32, color: Color, } Generated_Circle_Geometry :: struct { resource: Resource_Handle, cx, cy: f32, radius: f32, color: Color, } Generated_Billboard_Geometry :: struct { resource: Resource_Handle, position: Vec3, size: f32, color: Color, } Generated_Quad_3D_Geometry :: struct { resource: Resource_Handle, corners: [4]Vec3, normal: Vec3, color: Color, } Glyph_Quad :: struct { x, y: f32, width: f32, height: f32, u0, v0: f32, u1, v1: f32, color: Color, } Generated_Glyph_Geometry :: struct { resource: Resource_Handle, quads: [dynamic]Glyph_Quad, } Frame :: struct { ir: ir.Frame_IR, active_target: Target_Handle, layer_stack: [MAX_FRAME_LAYER_STACK]Layer_Handle, layer_count: int, submitted: bool, runtime: ^Engine_State, imported_meshes: [dynamic]Imported_Mesh_Geometry, imported_storage_buffers: [dynamic]Imported_Storage_Buffer, generated_rects: [dynamic]Generated_Rect_Geometry, generated_textured_quads: [dynamic]Generated_Textured_Quad_Geometry, generated_lines: [dynamic]Generated_Line_Geometry, generated_circles: [dynamic]Generated_Circle_Geometry, generated_billboards: [dynamic]Generated_Billboard_Geometry, generated_quads_3d: [dynamic]Generated_Quad_3D_Geometry, generated_raw_draws: [dynamic]Generated_Raw_Draw, generated_glyphs: [dynamic]Generated_Glyph_Geometry, generated_rect_indices: map[Resource_Handle]int, imported_storage_buffer_indices: map[Resource_Handle]int, generated_textured_quad_indices: map[Resource_Handle]int, generated_line_indices: map[Resource_Handle]int, generated_circle_indices: map[Resource_Handle]int, generated_billboard_indices: map[Resource_Handle]int, generated_quad_3d_indices: map[Resource_Handle]int, generated_raw_draw_indices: map[Resource_Handle]int, camera_3d: Camera3D, has_camera_3d: bool, lights_3d: [dynamic]Light, ambient_light_3d: Color, lighting_3d_enabled: bool, diagnostics: Diagnostic_List, } Rect_Draw_Desc :: struct { x, y: f32, width, height: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, material: Material_Handle, } Texture_Quad_Desc :: struct { texture: Texture, x, y: f32, width, height: f32, u0, v0: f32, u1, v1: f32, rotation: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, material: Material_Handle, } Offscreen_Texture_Quad_Desc :: struct { target: Target_Handle, x, y: f32, width, height: f32, u0, v0: f32, u1, v1: f32, rotation: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, material: Material_Handle, } Line_Draw_Desc :: struct { x0, y0: f32, x1, y1: f32, thickness: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, material: Material_Handle, } Circle_Draw_Desc :: struct { cx, cy: f32, radius: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, material: Material_Handle, } Text_Run_Desc :: struct { glyphs: []Glyph_Quad, material: Material_Handle, bounds: Bounds3, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Text_Draw_Desc :: struct { text: string, x, y: f32, color: Color, scale: f32, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Mesh_Draw_Desc :: struct { geometry: Geometry_View, instances: Instance_View, material: Material_Handle, transform: Mat4, bounds: Bounds3, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Mesh_Instance_Draw_Desc :: struct { mesh: Mesh, material: Material_Handle, transform: Mat4, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Model_Draw_Desc :: struct { model: Model, position: Vec3, scale: f32, tint: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Cube_Draw_Desc :: struct { center: Vec3, size: Vec3, material: Material_Handle, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Sphere_Draw_Desc :: struct { center: Vec3, radius: f32, material: Material_Handle, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Plane_Draw_Desc :: struct { center: Vec3, size: Vec2, material: Material_Handle, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Grid_Draw_Desc :: struct { slices: i32, spacing: f32, material: Material_Handle, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Billboard_Draw_Desc :: struct { position: Vec3, size: f32, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Quad_3D_Draw_Desc :: struct { corners: [4]Vec3, normal: Vec3, color: Color, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Dispatch_Desc :: struct { shader: Compute_Shader, groups: [3]u32, storage_bindings: []Storage_Buffer_Binding, push_constants: rawptr, push_constants_size: u32, barrier_after: bool, sort_key: u64, } Raw_Draw_Desc :: struct { callback: Raw_Frame_Draw_Proc, user_data: rawptr, sort_key: u64, order: Order_Mode, clip: Clip_Handle, } Offscreen_Target_Desc :: struct { name: string, width: u32, height: u32, color_format: ir.Format, depth_format: ir.Format, clear_color: Color, clear_depth: f32, } init_context :: proc( surface: Surface_Desc = {}, width: u32 = DEFAULT_WINDOW_WIDTH, height: u32 = DEFAULT_WINDOW_HEIGHT, title: cstring = "GPU", ) -> Context { ctx := Context { default_width = width, default_height = height, color_format = .B8G8R8A8_SRGB, depth_format = .D32_SFLOAT, } if surface.kind == .None { return ctx } created_runtime := false if g_state == nil || !g_state.initialized { if init_surface(surface, width, height, title) { created_runtime = true } } if g_state != nil && g_state.initialized { ctx.runtime = g_state ctx.owns_runtime = created_runtime } return ctx } destroy_context :: proc(ctx: ^Context) { if ctx == nil { return } if ctx.owns_runtime && ctx.runtime != nil && ctx.runtime == g_state { shutdown() } ctx^ = {} } context_is_runtime_backed :: proc(ctx: Context) -> bool { return ctx.runtime != nil && ctx.runtime.initialized } context_from_runtime :: proc() -> Context { if g_state == nil || !g_state.initialized { return init_context() } extent := g_state.backend.get_extent() return { default_width = extent.width, default_height = extent.height, color_format = .B8G8R8A8_SRGB, depth_format = .D32_SFLOAT, runtime = g_state, } } begin_frame :: proc(ctx: Context) -> Frame { return Frame { ir = ir.init_frame_ir(), runtime = ctx.runtime, imported_meshes = make([dynamic]Imported_Mesh_Geometry), imported_storage_buffers = make([dynamic]Imported_Storage_Buffer), generated_rects = make([dynamic]Generated_Rect_Geometry), generated_textured_quads = make([dynamic]Generated_Textured_Quad_Geometry), generated_lines = make([dynamic]Generated_Line_Geometry), generated_circles = make([dynamic]Generated_Circle_Geometry), generated_billboards = make([dynamic]Generated_Billboard_Geometry), generated_quads_3d = make([dynamic]Generated_Quad_3D_Geometry), generated_raw_draws = make([dynamic]Generated_Raw_Draw), generated_glyphs = make([dynamic]Generated_Glyph_Geometry), generated_rect_indices = make(map[Resource_Handle]int), imported_storage_buffer_indices = make(map[Resource_Handle]int), generated_textured_quad_indices = make(map[Resource_Handle]int), generated_line_indices = make(map[Resource_Handle]int), generated_circle_indices = make(map[Resource_Handle]int), generated_billboard_indices = make(map[Resource_Handle]int), generated_quad_3d_indices = make(map[Resource_Handle]int), generated_raw_draw_indices = make(map[Resource_Handle]int), lights_3d = make([dynamic]Light), ambient_light_3d = {0.1, 0.1, 0.1, 1.0}, diagnostics = ir.init_diagnostics(), } } destroy_frame :: proc(frame: ^Frame) { if frame == nil { return } for entry in frame.generated_glyphs { delete(entry.quads) } delete(frame.imported_meshes) delete(frame.imported_storage_buffers) delete(frame.generated_rects) delete(frame.generated_textured_quads) delete(frame.generated_lines) delete(frame.generated_circles) delete(frame.generated_billboards) delete(frame.generated_quads_3d) delete(frame.generated_raw_draws) delete(frame.generated_glyphs) delete(frame.generated_rect_indices) delete(frame.imported_storage_buffer_indices) delete(frame.generated_textured_quad_indices) delete(frame.generated_line_indices) delete(frame.generated_circle_indices) delete(frame.generated_billboard_indices) delete(frame.generated_quad_3d_indices) delete(frame.generated_raw_draw_indices) delete(frame.lights_3d) ir.destroy_diagnostics(&frame.diagnostics) ir.destroy_frame_ir(&frame.ir) frame^ = {} } frame_ir :: proc(frame: ^Frame) -> ^Frame_IR { if frame == nil { return nil } return &frame.ir } frame_diagnostics :: proc(frame: ^Frame) -> []Diagnostic { if frame == nil { return nil } return frame.diagnostics.items[:] } frame_is_runtime_backed :: proc(frame: ^Frame) -> bool { return frame != nil && frame.runtime != nil && frame.runtime.initialized } frame_runtime_plan :: proc( frame: ^Frame, allocator: mem.Allocator = context.allocator, ) -> [dynamic]Planned_Command { if frame == nil { return make([dynamic]Planned_Command, 0, 0, allocator) } return ir.plan_frame_commands(&frame.ir, allocator) } destroy_frame_runtime_plan :: proc(plan: ^[dynamic]Planned_Command) { ir.command_plan_destroy(plan) } set_camera_3d :: proc(frame: ^Frame, camera: Camera3D) { if frame == nil { return } frame.camera_3d = camera frame.has_camera_3d = true } set_ambient_light_frame :: proc(frame: ^Frame, color: Color) { if frame == nil { return } frame.ambient_light_3d = color frame.lighting_3d_enabled = true } add_light_frame :: proc(frame: ^Frame, light: Light) -> bool { if frame == nil { return false } if len(frame.lights_3d) >= renderer.MAX_LIGHTS { return false } append(&frame.lights_3d, light) frame.lighting_3d_enabled = true return true } present_target :: proc(ctx: Context, name: string = "present") -> Target_Desc { return { kind = .Present, name = name, width = ctx.default_width, height = ctx.default_height, color_format = ctx.color_format, depth_format = ctx.depth_format, clear_color = BLACK, clear_depth = 1, } } offscreen_target :: proc(ctx: Context, desc: Offscreen_Target_Desc) -> Target_Desc { color_format := desc.color_format if color_format == .Undefined { color_format = ctx.color_format } depth_format := desc.depth_format if depth_format == .Undefined { depth_format = ctx.depth_format } clear_depth := desc.clear_depth if clear_depth == 0 { clear_depth = 1 } return { kind = .Offscreen, name = desc.name, width = desc.width, height = desc.height, color_format = color_format, depth_format = depth_format, clear_color = desc.clear_color, clear_depth = clear_depth, } } begin_target :: proc(frame: ^Frame, desc: Target_Desc) -> Target_Handle { if frame == nil { return INVALID_TARGET } target := ir.add_target(&frame.ir, desc) frame.active_target = target return target } end_target :: proc(frame: ^Frame, target: Target_Handle) { if frame == nil { return } if frame.active_target == target { frame.active_target = INVALID_TARGET } } push_layer :: proc(frame: ^Frame, desc: Layer_Desc) -> Layer_Handle { if frame == nil || frame.layer_count >= MAX_FRAME_LAYER_STACK { return INVALID_LAYER } layer_desc := desc if layer_desc.target == INVALID_TARGET { layer_desc.target = frame.active_target } layer := ir.add_layer(&frame.ir, layer_desc) frame.layer_stack[frame.layer_count] = layer frame.layer_count += 1 return layer } pop_layer :: proc(frame: ^Frame) -> Layer_Handle { if frame == nil || frame.layer_count == 0 { return INVALID_LAYER } frame.layer_count -= 1 layer := frame.layer_stack[frame.layer_count] frame.layer_stack[frame.layer_count] = INVALID_LAYER return layer } current_layer :: proc(frame: ^Frame) -> Layer_Handle { if frame == nil || frame.layer_count == 0 { return INVALID_LAYER } return frame.layer_stack[frame.layer_count - 1] } add_material :: proc(frame: ^Frame, desc: Material_Desc) -> Material_Handle { if frame == nil { return INVALID_MATERIAL } return ir.add_material(&frame.ir, desc) } add_texture_material :: proc( frame: ^Frame, texture: Texture, desc: Material_Desc = {}, ) -> Material_Handle { if frame == nil || texture.id == 0 || texture.width <= 0 || texture.height <= 0 { return INVALID_MATERIAL } material_desc := desc material_desc.texture_id = texture.id return ir.add_material(&frame.ir, material_desc) } add_mesh_material :: proc( frame: ^Frame, material: Material, desc: Material_Desc = {}, ) -> Material_Handle { if frame == nil { return INVALID_MATERIAL } material_desc := desc material_desc.texture_id = material.diffuse.id material_desc.normal_map_texture_id = material.normal_map.id material_desc.base_color = material.color material_desc.double_sided = material.double_sided material_desc.metallic = material.metallic material_desc.roughness = material.roughness material_desc.reflectance = material.reflectance material_desc.emissive = material.emissive return ir.add_material(&frame.ir, material_desc) } @(private) runtime_texture_id_from_material :: proc(frame: ^Frame, material: Material_Handle) -> (u32, bool) { if frame == nil { return 0, false } desc, ok := ir.get_material(&frame.ir, material) if !ok || desc.texture_id == 0 { return 0, false } return desc.texture_id, true } import_mesh_geometry :: proc(frame: ^Frame, mesh: Mesh) -> Geometry_View { if frame == nil || mesh.id == 0 || mesh.index_count <= 0 || mesh.vertex_count <= 0 { return {} } resource := ir.add_buffer(&frame.ir, "imported-mesh", 0, {.Vertex, .Index}, {}, .Imported) append(&frame.imported_meshes, Imported_Mesh_Geometry{resource = resource, mesh = mesh}) return { kind = .Indexed_Mesh, resource = resource, vertex_count = u32(mesh.vertex_count), index_count = u32(mesh.index_count), } } @(private) find_imported_mesh_geometry :: proc(frame: ^Frame, resource: Resource_Handle) -> (Mesh, bool) { if frame == nil { return {}, false } for entry in frame.imported_meshes { if entry.resource == resource { return entry.mesh, true } } return {}, false } @(private) runtime_mesh_from_id :: proc(runtime: ^Engine_State, mesh_id: u32) -> (Mesh, bool) { if runtime == nil || mesh_id == 0 { return {}, false } vertex_count, index_count, _, ok := resource.get_mesh_info(&runtime.resource_state, mesh_id) if !ok { return {}, false } return {id = mesh_id, vertex_count = vertex_count, index_count = index_count}, true } @(private) color_is_zero :: proc(color: Color) -> bool { return color[0] == 0 && color[1] == 0 && color[2] == 0 && color[3] == 0 } @(private) color_or_default :: proc(color, fallback: Color) -> Color { return fallback if color_is_zero(color) else color } @(private) color_mul :: proc(a, b: Color) -> Color { return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]} } import_storage_buffer :: proc( frame: ^Frame, buffer: Storage_Buffer, name: string = "imported-storage-buffer", ) -> Storage_Buffer_View { if frame == nil || buffer.id == 0 || buffer.size <= 0 { return {} } resource := ir.add_buffer(&frame.ir, name, u64(buffer.size), {.Storage}, {}, .Imported) append( &frame.imported_storage_buffers, Imported_Storage_Buffer{resource = resource, buffer = buffer}, ) frame.imported_storage_buffer_indices[resource] = len(frame.imported_storage_buffers) - 1 return {resource = resource, size = u64(buffer.size)} } @(private) find_imported_storage_buffer :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Storage_Buffer, bool, ) { if frame == nil { return {}, false } if index, ok := frame.imported_storage_buffer_indices[resource]; ok { if index >= 0 && index < len(frame.imported_storage_buffers) { entry := frame.imported_storage_buffers[index] if entry.resource == resource { return entry.buffer, true } } } for entry in frame.imported_storage_buffers { if entry.resource == resource { return entry.buffer, true } } return {}, false } @(private) runtime_builtin_mesh :: proc(frame: ^Frame, mesh_id: u32) -> (Mesh, bool) { if frame == nil || frame.runtime == nil { return {}, false } return runtime_mesh_from_id(frame.runtime, mesh_id) } @(private) runtime_grid_mesh :: proc(frame: ^Frame, slices: i32, spacing: f32) -> (Mesh, bool) { if frame == nil || frame.runtime == nil || slices < 0 || spacing <= 0 { return {}, false } rs := &frame.runtime.render_state if !rs.grid_valid || rs.grid_slices != slices || rs.grid_spacing != spacing { if rs.grid_valid { resource.unload_mesh( &frame.runtime.resource_state, &frame.runtime.backend, rs.grid_mesh, ) rs.grid_valid = false } verts, idxs := resource.gen_grid_geometry(slices, spacing, 0.01) defer delete(verts) defer delete(idxs) id, _, ok := resource.upload_mesh_raw( &frame.runtime.resource_state, &frame.runtime.backend, raw_data(verts), len(verts) * size_of(resource.Mesh_Vertex_PNU), i32(len(verts)), idxs, bk.LAYOUT_POS_NORM_UV, ) if !ok { return {}, false } rs.grid_mesh = id rs.grid_slices = slices rs.grid_spacing = spacing rs.grid_valid = true } return runtime_mesh_from_id(frame.runtime, rs.grid_mesh) } @(private) add_generated_glyph_geometry :: proc(frame: ^Frame, glyphs: []Glyph_Quad) -> Geometry_View { if frame == nil || len(glyphs) == 0 { return {} } resource := ir.add_buffer(&frame.ir, "generated-glyphs", 0, {.Vertex}, {}, .Transient) entry := Generated_Glyph_Geometry { resource = resource, quads = make([dynamic]Glyph_Quad, 0, len(glyphs)), } for glyph in glyphs { append(&entry.quads, glyph) } append(&frame.generated_glyphs, entry) return {kind = .Glyph_Quad_Stream, resource = resource, vertex_count = u32(len(glyphs) * 6)} } @(private) find_generated_glyph_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Glyph_Geometry, bool, ) { if frame == nil { return {}, false } for entry in frame.generated_glyphs { if entry.resource == resource { return entry, true } } return {}, false } @(private) add_generated_rect_geometry :: proc(frame: ^Frame, desc: Rect_Draw_Desc) -> Geometry_View { if frame == nil || desc.width <= 0 || desc.height <= 0 { return {} } resource := ir.add_buffer(&frame.ir, "generated-rect", 0, {.Vertex}, {}, .Transient) rect_index := len(frame.generated_rects) append( &frame.generated_rects, Generated_Rect_Geometry { resource = resource, x = desc.x, y = desc.y, width = desc.width, height = desc.height, color = desc.color, }, ) frame.generated_rect_indices[resource] = rect_index return {kind = .Quad_Stream, resource = resource, vertex_count = 6} } @(private) find_generated_rect_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Rect_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_rect_indices[resource]; ok && index >= 0 && index < len(frame.generated_rects) { return frame.generated_rects[index], true } return {}, false } @(private) add_generated_textured_quad_geometry :: proc( frame: ^Frame, desc: Texture_Quad_Desc, ) -> Geometry_View { if frame == nil || desc.texture.id == 0 { return {} } width := desc.width height := desc.height if width <= 0 { width = f32(desc.texture.width) } if height <= 0 { height = f32(desc.texture.height) } if width <= 0 || height <= 0 { return {} } u0 := desc.u0 v0 := desc.v0 u1 := desc.u1 v1 := desc.v1 if u0 == 0 && v0 == 0 && u1 == 0 && v1 == 0 { u0 = 0 v0 = 1 u1 = 1 v1 = 0 } resource := ir.add_buffer(&frame.ir, "generated-textured-quad", 0, {.Vertex}, {}, .Transient) quad_index := len(frame.generated_textured_quads) append( &frame.generated_textured_quads, Generated_Textured_Quad_Geometry { resource = resource, texture_id = desc.texture.id, sampled_resource = INVALID_RESOURCE, x = desc.x, y = desc.y, width = width, height = height, u0 = u0, v0 = v0, u1 = u1, v1 = v1, rotation = desc.rotation, color = desc.color, }, ) frame.generated_textured_quad_indices[resource] = quad_index return {kind = .Quad_Stream, resource = resource, vertex_count = 6} } @(private) offscreen_target_color_resource :: proc( frame: ^Frame, target_handle: Target_Handle, ) -> ( Resource_Handle, u32, u32, bool, ) { if frame == nil || target_handle == INVALID_TARGET { return INVALID_RESOURCE, 0, 0, false } if !compiler.materialize_frame_targets(&frame.ir) { return INVALID_RESOURCE, 0, 0, false } target, target_ok := ir.get_target(&frame.ir, target_handle) if !target_ok || target.kind != .Offscreen || target.color_resource == INVALID_RESOURCE { return INVALID_RESOURCE, 0, 0, false } resource_desc, resource_ok := ir.get_resource(&frame.ir, target.color_resource) if !resource_ok || resource_desc.kind != .Texture || !(.Sampled in resource_desc.texture.usage) { return INVALID_RESOURCE, 0, 0, false } return target.color_resource, resource_desc.texture.width, resource_desc.texture.height, true } @(private) add_generated_offscreen_textured_quad_geometry :: proc( frame: ^Frame, desc: Offscreen_Texture_Quad_Desc, ) -> Geometry_View { if frame == nil || frame.active_target == desc.target { return {} } sampled_resource, target_width, target_height, sampled_ok := offscreen_target_color_resource( frame, desc.target, ) if !sampled_ok { return {} } width := desc.width height := desc.height if width <= 0 { width = f32(target_width) } if height <= 0 { height = f32(target_height) } if width <= 0 || height <= 0 { return {} } u0 := desc.u0 v0 := desc.v0 u1 := desc.u1 v1 := desc.v1 if u0 == 0 && v0 == 0 && u1 == 0 && v1 == 0 { u0 = 0 v0 = 1 u1 = 1 v1 = 0 } resource := ir.add_buffer(&frame.ir, "sampled-offscreen-quad", 0, {.Vertex}, {}, .Transient) quad_index := len(frame.generated_textured_quads) append( &frame.generated_textured_quads, Generated_Textured_Quad_Geometry { resource = resource, texture_id = 0, sampled_resource = sampled_resource, x = desc.x, y = desc.y, width = width, height = height, u0 = u0, v0 = v0, u1 = u1, v1 = v1, rotation = desc.rotation, color = desc.color, }, ) frame.generated_textured_quad_indices[resource] = quad_index return {kind = .Quad_Stream, resource = resource, vertex_count = 6} } find_generated_textured_quad_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Textured_Quad_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_textured_quad_indices[resource]; ok && index >= 0 && index < len(frame.generated_textured_quads) { return frame.generated_textured_quads[index], true } return {}, false } @(private) add_generated_line_geometry :: proc(frame: ^Frame, desc: Line_Draw_Desc) -> Geometry_View { if frame == nil || desc.x0 == desc.x1 && desc.y0 == desc.y1 { return {} } thickness := desc.thickness if thickness <= 0 { thickness = 1 } resource := ir.add_buffer(&frame.ir, "generated-line", 0, {.Vertex}, {}, .Transient) line_index := len(frame.generated_lines) append( &frame.generated_lines, Generated_Line_Geometry { resource = resource, x0 = desc.x0, y0 = desc.y0, x1 = desc.x1, y1 = desc.y1, thickness = thickness, color = desc.color, }, ) frame.generated_line_indices[resource] = line_index return {kind = .Line_Stream, resource = resource, vertex_count = 6} } @(private) find_generated_line_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Line_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_line_indices[resource]; ok && index >= 0 && index < len(frame.generated_lines) { return frame.generated_lines[index], true } return {}, false } @(private) add_generated_circle_geometry :: proc(frame: ^Frame, desc: Circle_Draw_Desc) -> Geometry_View { if frame == nil || desc.radius <= 0 { return {} } resource := ir.add_buffer(&frame.ir, "generated-circle", 0, {.Vertex}, {}, .Transient) circle_index := len(frame.generated_circles) append( &frame.generated_circles, Generated_Circle_Geometry { resource = resource, cx = desc.cx, cy = desc.cy, radius = desc.radius, color = desc.color, }, ) frame.generated_circle_indices[resource] = circle_index return { kind = .Vertex_Stream, resource = resource, vertex_count = u32(1 + renderer.CIRCLE_SEGMENTS), } } @(private) find_generated_circle_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Circle_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_circle_indices[resource]; ok && index >= 0 && index < len(frame.generated_circles) { return frame.generated_circles[index], true } return {}, false } @(private) add_generated_billboard_geometry :: proc( frame: ^Frame, desc: Billboard_Draw_Desc, ) -> Geometry_View { if frame == nil || desc.size <= 0 { return {} } resource := ir.add_buffer(&frame.ir, "generated-billboard", 0, {.Vertex}, {}, .Transient) billboard_index := len(frame.generated_billboards) append( &frame.generated_billboards, Generated_Billboard_Geometry { resource = resource, position = desc.position, size = desc.size, color = desc.color, }, ) frame.generated_billboard_indices[resource] = billboard_index return {kind = .Point_Stream, resource = resource, vertex_count = 1} } @(private) find_generated_billboard_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Billboard_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_billboard_indices[resource]; ok && index >= 0 && index < len(frame.generated_billboards) { return frame.generated_billboards[index], true } return {}, false } @(private) add_generated_quad_3d_geometry :: proc(frame: ^Frame, desc: Quad_3D_Draw_Desc) -> Geometry_View { if frame == nil || color_is_zero(desc.color) { return {} } resource := ir.add_buffer(&frame.ir, "generated-quad-3d", 0, {.Vertex}, {}, .Transient) normal := desc.normal if normal.x == 0 && normal.y == 0 && normal.z == 0 { normal = {0, 1, 0} } quad_index := len(frame.generated_quads_3d) append( &frame.generated_quads_3d, Generated_Quad_3D_Geometry { resource = resource, corners = desc.corners, normal = normal, color = desc.color, }, ) frame.generated_quad_3d_indices[resource] = quad_index return {kind = .Vertex_Stream, resource = resource, vertex_count = 4, index_count = 6} } @(private) find_generated_quad_3d_geometry :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Quad_3D_Geometry, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_quad_3d_indices[resource]; ok && index >= 0 && index < len(frame.generated_quads_3d) { return frame.generated_quads_3d[index], true } return {}, false } @(private) add_generated_raw_draw :: proc(frame: ^Frame, desc: Raw_Draw_Desc) -> Geometry_View { if frame == nil || desc.callback == nil { return {} } resource := ir.add_buffer(&frame.ir, "raw-draw", 0, {}, {}, .Transient) raw_index := len(frame.generated_raw_draws) append( &frame.generated_raw_draws, Generated_Raw_Draw { resource = resource, callback = desc.callback, user_data = desc.user_data, }, ) frame.generated_raw_draw_indices[resource] = raw_index return {kind = .Raw, resource = resource} } @(private) find_generated_raw_draw :: proc( frame: ^Frame, resource: Resource_Handle, ) -> ( Generated_Raw_Draw, bool, ) { if frame == nil { return {}, false } if index, ok := frame.generated_raw_draw_indices[resource]; ok && index >= 0 && index < len(frame.generated_raw_draws) { return frame.generated_raw_draws[index], true } return {}, false } add_clip :: proc(frame: ^Frame, desc: Clip_Desc) -> Clip_Handle { if frame == nil { return INVALID_CLIP } clip_desc := desc if clip_desc.target == INVALID_TARGET { clip_desc.target = frame.active_target } return ir.add_clip(&frame.ir, clip_desc) } normalize_packet :: proc(frame: ^Frame, packet: Draw_Packet) -> Draw_Packet { p := packet if p.target == INVALID_TARGET { p.target = frame.active_target } if p.layer == INVALID_LAYER { p.layer = current_layer(frame) } return p } draw_packet :: proc(frame: ^Frame, kind: Draw_Kind, packet: Draw_Packet) -> Command_Handle { if frame == nil { return INVALID_COMMAND } p := normalize_packet(frame, packet) instance_count := p.instances.count if instance_count == 0 { instance_count = 1 } return ir.add_draw_packet( &frame.ir, ir.INVALID_PASS, ir.INVALID_PIPELINE, kind, p, p.geometry.vertex_count, instance_count, ) } draw_rect :: proc(frame: ^Frame, desc: Rect_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_rect_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Quad_Stream, { sort_key = desc.sort_key, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_texture_quad :: proc(frame: ^Frame, desc: Texture_Quad_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_textured_quad_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Quad_Stream, { sort_key = desc.sort_key, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_offscreen_texture_quad :: proc( frame: ^Frame, desc: Offscreen_Texture_Quad_Desc, ) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_offscreen_textured_quad_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Quad_Stream, { sort_key = desc.sort_key, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_line_frame :: proc(frame: ^Frame, desc: Line_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_line_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Line_Stream, { sort_key = desc.sort_key, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_circle_frame :: proc(frame: ^Frame, desc: Circle_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_circle_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Vertex_Stream, { sort_key = desc.sort_key, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_billboard_frame :: proc(frame: ^Frame, desc: Billboard_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_billboard_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Point_Stream, { sort_key = desc.sort_key, clip = desc.clip, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_quad_3d_frame :: proc(frame: ^Frame, desc: Quad_3D_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_quad_3d_geometry(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Vertex_Stream, { sort_key = desc.sort_key, clip = desc.clip, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_raw_frame :: proc(frame: ^Frame, desc: Raw_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } geometry := add_generated_raw_draw(frame, desc) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .External, { sort_key = desc.sort_key, clip = desc.clip, geometry = geometry, instances = {count = 1}, order = desc.order, }, ) } draw_text_run :: proc(frame: ^Frame, desc: Text_Run_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } order := desc.order geometry := add_generated_glyph_geometry(frame, desc.glyphs) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_packet( frame, .Glyph_Quad_Stream, { sort_key = desc.sort_key, bounds = desc.bounds, clip = desc.clip, material = desc.material, geometry = geometry, instances = {count = 1}, order = order, }, ) } draw_text_frame :: proc(frame: ^Frame, desc: Text_Draw_Desc) { if frame == nil || desc.scale <= 0 { return } cursor_x := desc.x cursor_y := desc.y sequence: u64 for i in 0 ..< len(desc.text) { ch := desc.text[i] if ch == '\n' { cursor_x = desc.x cursor_y += f32(renderer.BASE_FONT_LINE_ADVANCE) * desc.scale continue } if ch == ' ' || ch == '\t' { cursor_x += f32(renderer.BASE_FONT_ADVANCE) * desc.scale continue } rows := renderer.base_font_glyph(ch) for row in 0 ..< renderer.BASE_FONT_HEIGHT { bits := rows[row] col := 0 for col < renderer.BASE_FONT_WIDTH { mask := u8(1 << u32(renderer.BASE_FONT_WIDTH - 1 - col)) if (bits & mask) == 0 { col += 1 continue } start_col := col for col < renderer.BASE_FONT_WIDTH { run_mask := u8(1 << u32(renderer.BASE_FONT_WIDTH - 1 - col)) if (bits & run_mask) == 0 { break } col += 1 } _ = draw_rect( frame, { x = cursor_x + f32(start_col) * desc.scale, y = cursor_y + f32(row) * desc.scale, width = f32(col - start_col) * desc.scale, height = desc.scale, color = desc.color, sort_key = desc.sort_key + sequence, order = desc.order, clip = desc.clip, }, ) sequence += 1 } } cursor_x += f32(renderer.BASE_FONT_ADVANCE) * desc.scale } } draw_mesh_frame :: proc(frame: ^Frame, desc: Mesh_Draw_Desc) -> Command_Handle { order := desc.order return draw_packet( frame, .Mesh, { sort_key = desc.sort_key, bounds = desc.bounds, clip = desc.clip, transform = desc.transform, material = desc.material, geometry = desc.geometry, instances = desc.instances, order = order, }, ) } draw_instances :: proc(frame: ^Frame, desc: Mesh_Draw_Desc) -> Command_Handle { order := desc.order return draw_packet( frame, .Indexed_Mesh, { sort_key = desc.sort_key, bounds = desc.bounds, clip = desc.clip, transform = desc.transform, material = desc.material, geometry = desc.geometry, instances = desc.instances, order = order, }, ) } draw_mesh_instance_frame :: proc(frame: ^Frame, desc: Mesh_Instance_Draw_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } geometry := import_mesh_geometry(frame, desc.mesh) if geometry.resource == INVALID_RESOURCE { return INVALID_COMMAND } return draw_instances( frame, { geometry = geometry, instances = {count = 1}, material = desc.material, transform = desc.transform, sort_key = desc.sort_key, order = desc.order, clip = desc.clip, }, ) } draw_model_frame :: proc(frame: ^Frame, desc: Model_Draw_Desc) -> u32 { if frame == nil || len(desc.model.meshes) == 0 { return 0 } scale := desc.scale if scale == 0 { scale = 1 } tint := desc.tint if color_is_zero(tint) { tint = WHITE } base_transform := mat4_translate(desc.position) * mat4_scale({scale, scale, scale}) * desc.model.transform drawn: u32 for i in 0 ..< len(desc.model.meshes) { material := INVALID_MATERIAL if i < len(desc.model.materials) { model_material := desc.model.materials[i] model_material.color = color_mul(color_or_default(model_material.color, WHITE), tint) material = add_mesh_material(frame, model_material) } cmd := draw_mesh_instance_frame( frame, { mesh = desc.model.meshes[i], material = material, transform = base_transform, sort_key = desc.sort_key + u64(i), order = desc.order, clip = desc.clip, }, ) if cmd != INVALID_COMMAND { drawn += 1 } } return drawn } draw_cube_frame :: proc(frame: ^Frame, desc: Cube_Draw_Desc) -> Command_Handle { if frame == nil || desc.size.x <= 0 || desc.size.y <= 0 || desc.size.z <= 0 { return INVALID_COMMAND } mesh, ok := runtime_builtin_mesh( frame, frame.runtime.render_state.builtin_cube if frame.runtime != nil else 0, ) if !ok { return INVALID_COMMAND } return draw_mesh_instance_frame( frame, { mesh = mesh, material = desc.material, transform = mat4_translate(desc.center) * mat4_scale(desc.size), sort_key = desc.sort_key, order = desc.order, clip = desc.clip, }, ) } draw_sphere_frame :: proc(frame: ^Frame, desc: Sphere_Draw_Desc) -> Command_Handle { if frame == nil || desc.radius <= 0 { return INVALID_COMMAND } mesh, ok := runtime_builtin_mesh( frame, frame.runtime.render_state.builtin_sphere if frame.runtime != nil else 0, ) if !ok { return INVALID_COMMAND } return draw_mesh_instance_frame( frame, { mesh = mesh, material = desc.material, transform = mat4_translate(desc.center) * mat4_scale({desc.radius, desc.radius, desc.radius}), sort_key = desc.sort_key, order = desc.order, clip = desc.clip, }, ) } draw_plane_frame :: proc(frame: ^Frame, desc: Plane_Draw_Desc) -> Command_Handle { if frame == nil || desc.size.x <= 0 || desc.size.y <= 0 { return INVALID_COMMAND } mesh, ok := runtime_builtin_mesh( frame, frame.runtime.render_state.builtin_plane if frame.runtime != nil else 0, ) if !ok { return INVALID_COMMAND } return draw_mesh_instance_frame( frame, { mesh = mesh, material = desc.material, transform = mat4_translate(desc.center) * mat4_scale({desc.size.x, 1, desc.size.y}), sort_key = desc.sort_key, order = desc.order, clip = desc.clip, }, ) } draw_grid_frame :: proc(frame: ^Frame, desc: Grid_Draw_Desc) -> Command_Handle { mesh, ok := runtime_grid_mesh(frame, desc.slices, desc.spacing) if !ok { return INVALID_COMMAND } return draw_mesh_instance_frame( frame, { mesh = mesh, material = desc.material, transform = mat4_translate({0, GRID_Y_OFFSET_3D, 0}), sort_key = desc.sort_key, order = desc.order, clip = desc.clip, }, ) } dispatch :: proc(frame: ^Frame, desc: Dispatch_Desc) -> Command_Handle { if frame == nil { return INVALID_COMMAND } if desc.shader.id == 0 || desc.groups[0] == 0 || desc.groups[1] == 0 || desc.groups[2] == 0 { return INVALID_COMMAND } if len(desc.storage_bindings) > ir.MAX_COMPUTE_BINDINGS { return INVALID_COMMAND } storage_bindings: [ir.MAX_COMPUTE_BINDINGS]ir.Descriptor_Resource_Binding seen_bindings: bit_set[0 ..< ir.MAX_COMPUTE_BINDINGS] for storage_binding, i in desc.storage_bindings { if storage_binding.buffer.resource == INVALID_RESOURCE || storage_binding.buffer.size == 0 { return INVALID_COMMAND } if storage_binding.binding >= ir.MAX_COMPUTE_BINDINGS { return INVALID_COMMAND } binding_index := int(storage_binding.binding) if binding_index in seen_bindings { return INVALID_COMMAND } seen_bindings += {binding_index} resource, resource_ok := ir.get_resource(&frame.ir, storage_binding.buffer.resource) if !resource_ok || resource.kind != .Buffer || !(.Storage in resource.buffer.usage) { return INVALID_COMMAND } storage_bindings[i] = { binding = storage_binding.binding, type = .Storage_Buffer, resource = storage_binding.buffer.resource, size = storage_binding.buffer.size, } } push_constants: []u8 if desc.push_constants != nil && desc.push_constants_size > 0 { push_constants = (cast([^]u8)desc.push_constants)[:int(desc.push_constants_size)] } return ir.add_dispatch( &frame.ir, { target = frame.active_target, layer = current_layer(frame), sort_key = desc.sort_key, groups = desc.groups, shader_id = desc.shader.id, storage_bindings = storage_bindings, storage_binding_count = u8(len(desc.storage_bindings)), push_constants = push_constants, barrier_after = desc.barrier_after, }, ) } submit :: proc(frame: ^Frame) -> bool { if frame == nil { return false } if !compiler.materialize_frame_targets(&frame.ir) { return false } offscreen_exec: compiler.Execution_State offscreen_exec_ok := false defer if offscreen_exec_ok { compiler.destroy_execution_state_views(&offscreen_exec) } if frame.runtime != nil && frame_has_offscreen_targets(frame) { offscreen_exec = compiler.init_execution_state( &frame.runtime.backend, allocator = context.allocator, ) frame_index := frame.runtime.backend.current_frame_index() if !compiler.prepare_render_targets_cached( &offscreen_exec, &frame.ir, &frame.runtime.offscreen_targets, frame_index, ) { return false } offscreen_exec_ok = true } ir.reset_diagnostics(&frame.diagnostics) ir.validate_intent_handles(&frame.ir, &frame.diagnostics) validate_runtime_target_support(frame, &frame.diagnostics) if ir.has_errors(&frame.diagnostics) { log_frame_diagnostics(frame) return false } if frame.runtime != nil { if !frame.runtime.initialized { return false } if offscreen_exec_ok { if !submit_runtime_frame(frame, &offscreen_exec) { log_frame_diagnostics(frame) return false } } else if !submit_runtime_frame(frame) { log_frame_diagnostics(frame) return false } } frame.submitted = true return true } @(private) frame_has_offscreen_targets :: proc(frame: ^Frame) -> bool { if frame == nil { return false } for target in frame.ir.targets { if target.kind == .Offscreen { return true } } return false } @(private) validate_runtime_target_support :: proc(frame: ^Frame, diagnostics: ^ir.Diagnostic_List) { if frame == nil { return } } @(private) log_frame_diagnostics :: proc(frame: ^Frame) { if frame == nil || len(frame.diagnostics.items) == 0 { return } for d in frame.diagnostics.items { msg := ir.format_diagnostic(d, allocator = context.temp_allocator) log.errorf("gpu/frame: %s", msg) } } @(private) Runtime_Mesh_Draw :: struct { mesh: Mesh, layout_variant: bk.Layout_Variant, model: Mat4, texture_id: u32, normal_map_id: u32, double_sided: bool, base_color: Color, metallic: f32, roughness: f32, reflectance: f32, emissive: Color, } Runtime_Billboard_Draw :: struct { position: Vec3, size: f32, color: Color, } Runtime_Quad_3D_Draw :: struct { corners: [4]Vec3, normal: Vec3, color: Color, } Runtime_Raw_Draw :: struct { callback: Raw_Frame_Draw_Proc, user_data: rawptr, } @(private) DEFAULT_RUNTIME_MESH_BASE_COLOR :: Color{1, 1, 1, 1} @(private) DEFAULT_RUNTIME_MESH_ROUGHNESS :: f32(0.5) @(private) DEFAULT_RUNTIME_MESH_REFLECTANCE :: f32(0.5) @(private) DEFAULT_RUNTIME_MESH_EMISSIVE :: Color{0, 0, 0, 0} @(private) runtime_texture_active :: proc(runtime: ^Engine_State, texture_id: u32) -> bool { if runtime == nil || texture_id == 0 { return true } return texture_id < resource.MAX_TEXTURES && runtime.resource_state.textures[texture_id].active } @(private) runtime_mesh_material_from_handle :: proc( frame: ^Frame, material: Material_Handle, ) -> ( Runtime_Mesh_Draw, bool, ) { result := Runtime_Mesh_Draw { normal_map_id = resource.NORMAL_TEXTURE_ID, base_color = DEFAULT_RUNTIME_MESH_BASE_COLOR, roughness = DEFAULT_RUNTIME_MESH_ROUGHNESS, reflectance = DEFAULT_RUNTIME_MESH_REFLECTANCE, emissive = DEFAULT_RUNTIME_MESH_EMISSIVE, } if material == INVALID_MATERIAL { return result, true } desc, ok := ir.get_material(&frame.ir, material) if !ok { return {}, false } if frame.runtime != nil { if !runtime_texture_active(frame.runtime, desc.texture_id) { return {}, false } if desc.normal_map_texture_id != 0 && !runtime_texture_active(frame.runtime, desc.normal_map_texture_id) { return {}, false } } result.texture_id = desc.texture_id result.normal_map_id = desc.normal_map_texture_id if desc.normal_map_texture_id != 0 else resource.NORMAL_TEXTURE_ID result.double_sided = desc.double_sided result.base_color = color_or_default(desc.base_color, DEFAULT_RUNTIME_MESH_BASE_COLOR) result.metallic = desc.metallic result.roughness = desc.roughness if desc.roughness > 0 else DEFAULT_RUNTIME_MESH_ROUGHNESS result.reflectance = desc.reflectance if desc.reflectance > 0 else DEFAULT_RUNTIME_MESH_REFLECTANCE result.emissive = desc.emissive return result, true } Runtime_Dispatch :: struct { command: renderer.Runtime_Compute_Dispatch, } Runtime_Generated_2D_Draw_Kind :: enum { Rect, Textured_Quad, Line, Circle, Glyphs, } Runtime_Generated_2D_Draw :: struct { kind: Runtime_Generated_2D_Draw_Kind, rect: Generated_Rect_Geometry, textured_quad: Generated_Textured_Quad_Geometry, line: Generated_Line_Geometry, circle: Generated_Circle_Geometry, glyphs: Generated_Glyph_Geometry, texture_id: u32, } Runtime_Target :: struct { handle: Target_Handle, kind: ir.Target_Kind, pass: ir.Pass_Handle, width: u32, height: u32, } Runtime_Frame_Mode :: enum { Empty, Dispatches, Generated_2D, Meshes, Billboards, Raw, Mixed, } Frame_Planned_Coverage_Path :: enum { Empty, Planned, Migration, } Runtime_Command_Kind :: enum { Dispatch, Generated_2D, Mesh, Billboard_3D, Quad_3D, Raw, } Runtime_Command :: struct { kind: Runtime_Command_Kind, target: Runtime_Target, dispatch: Runtime_Dispatch, generated_2d: Runtime_Generated_2D_Draw, mesh: Runtime_Mesh_Draw, billboard: Runtime_Billboard_Draw, quad_3d: Runtime_Quad_3D_Draw, raw: Runtime_Raw_Draw, } @(private) submit_runtime_frame :: proc( frame: ^Frame, offscreen_exec: ^compiler.Execution_State = nil, ) -> bool { if ir.command_count(&frame.ir) == 0 { clear_color := frame_present_clear_color(frame) renderer.begin_frame(&frame.runtime.render_state, clear_color) if !frame.runtime.render_state.frame_active { return false } _ = renderer.end_frame(&frame.runtime.render_state) return true } plan := frame_runtime_plan(frame, context.temp_allocator) defer destroy_frame_runtime_plan(&plan) if frame_can_use_planned_executor(frame) { imported_draw_ctx := Planned_Imported_Draw_Context { frame = frame, } exec := compiler.init_execution_state( &frame.runtime.backend, allocator = context.temp_allocator, ) defer compiler.destroy_execution_state(&exec) compiler.set_imported_draw_resolver( &exec, planned_imported_draw_resolver, &imported_draw_ctx, ) compiler.set_planned_frame_begin_proc( &exec, planned_runtime_frame_begin, &imported_draw_ctx, ) if !compiler.prepare_planned_frame(&exec, &frame.ir) { return false } return compiler.execute_prepared_planned_frame( &exec, &frame.ir, frame_present_clear_color(frame), &frame.diagnostics, ) } commands := make( [dynamic]Runtime_Command, 0, ir.command_count(&frame.ir), context.temp_allocator, ) defer delete(commands) mode, mode_ok := collect_runtime_commands( frame, plan[:], &commands, offscreen_exec, &frame.diagnostics, ) if !mode_ok || mode == .Empty { return false } return submit_runtime_mixed_frame(frame, commands[:], offscreen_exec) } @(private) Planned_Imported_Draw_Context :: struct { frame: ^Frame, } @(private) planned_runtime_frame_begin :: proc(user_data: rawptr, ctx: bk.Frame_Context) -> bool { imported_ctx := cast(^Planned_Imported_Draw_Context)user_data if imported_ctx == nil || imported_ctx.frame == nil || imported_ctx.frame.runtime == nil { return false } frame := imported_ctx.frame rs := &frame.runtime.render_state rs.frame_ctx = ctx if frame_has_planned_generated_2d(frame) { renderer.reset_batch_2d(&rs.batch_2d) renderer.begin_batch_frame(&rs.batch_2d, &frame.runtime.backend, ctx) rs.batch_2d.parent_state = nil } if frame_has_planned_imported_mesh(frame) { configure_runtime_mesh_phase(frame, ctx.frame_index) } return true } @(private) frame_can_use_planned_executor :: proc(frame: ^Frame) -> bool { if frame == nil || frame.runtime == nil || ir.command_count(&frame.ir) == 0 { return false } for i in 0 ..< len(frame.ir.commands) { handle := ir.Command_Handle(i + 1) record := frame.ir.commands[i] if record.kind != .Draw { return false } draw, draw_ok := ir.get_draw(&frame.ir, handle) if !draw_ok || !planned_executor_can_draw(frame, draw) { return false } } return true } @(private) frame_has_planned_imported_mesh :: proc(frame: ^Frame) -> bool { if frame == nil { return false } for i in 0 ..< len(frame.ir.commands) { record := frame.ir.commands[i] if record.kind != .Draw { continue } draw, draw_ok := ir.get_draw(&frame.ir, ir.Command_Handle(i + 1)) if draw_ok && planned_draw_uses_imported_resource(frame, draw) { return true } } return false } @(private) frame_has_planned_generated_2d :: proc(frame: ^Frame) -> bool { if frame == nil { return false } for i in 0 ..< len(frame.ir.commands) { record := frame.ir.commands[i] if record.kind != .Draw { continue } draw, draw_ok := ir.get_draw(&frame.ir, ir.Command_Handle(i + 1)) if draw_ok && planned_draw_uses_generated_2d_resource(frame, draw) { return true } } return false } @(private) planned_executor_can_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { switch draw.kind { case .Mesh, .Indexed_Mesh, .Vertex_Stream, .Quad_Stream, .Line_Stream, .Point_Stream, .Glyph_Quad_Stream, .Indirect: case .Raw, .External: return false } if draw.packet.geometry.resource == ir.INVALID_RESOURCE { return false } if planned_draw_uses_imported_resource(frame, draw) { return planned_executor_can_imported_mesh_draw(frame, draw) } if planned_draw_uses_generated_2d_resource(frame, draw) { return planned_executor_can_generated_2d_draw(frame, draw) } pipeline := draw.pipeline if pipeline == ir.INVALID_PIPELINE { material, material_ok := ir.get_material(&frame.ir, draw.packet.material) if !material_ok { return false } pipeline = material.pipeline } pipeline_desc, pipeline_ok := ir.get_pipeline(&frame.ir, pipeline) if !pipeline_ok || pipeline_desc.kind != .Graphics { return false } if draw.kind == .Indirect || draw.packet.instances.indirect != ir.INVALID_RESOURCE { return planned_executor_can_indirect_draw(draw) && planned_executor_can_instance_draw(draw, pipeline_desc) } if draw.packet.instances.resource != ir.INVALID_RESOURCE { return planned_executor_can_instance_draw(draw, pipeline_desc) } return true } @(private) planned_executor_can_indirect_draw :: proc(draw: ^ir.Draw_Command) -> bool { if draw == nil { return false } if draw.packet.geometry.resource == ir.INVALID_RESOURCE { return false } if draw.packet.instances.indirect == ir.INVALID_RESOURCE { return false } if (draw.packet.instances.offset & 3) != 0 { return false } draw_count := draw.packet.instances.count if draw_count == 0 { draw_count = 1 } if draw_count != 1 { return false } indexed := draw.packet.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 } return stride >= min_stride && (stride & 3) == 0 } @(private) planned_draw_uses_imported_resource :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { if frame == nil || draw == nil || draw.packet.geometry.resource == ir.INVALID_RESOURCE { return false } resource_desc, resource_ok := ir.get_resource(&frame.ir, draw.packet.geometry.resource) return resource_ok && resource_desc.lifetime == .Imported } @(private) planned_executor_can_instance_draw :: proc( draw: ^ir.Draw_Command, pipeline: ^ir.Pipeline_Desc, ) -> bool { if draw == nil || pipeline == nil || pipeline.kind != .Graphics { return false } if draw.packet.instances.resource == INVALID_RESOURCE { return true } return pipeline.graphics.has_instance_layout && draw.packet.instances.stride > 0 } @(private) planned_draw_uses_generated_2d_resource :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { if frame == nil || draw == nil || draw.packet.geometry.resource == INVALID_RESOURCE { return false } resource_desc, resource_ok := ir.get_resource(&frame.ir, draw.packet.geometry.resource) return resource_ok && resource_desc.kind == .Buffer && resource_desc.lifetime == .Transient && resource_desc.buffer.size == 0 } @(private) planned_executor_can_generated_2d_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { return planned_executor_can_generated_2d_draw_with_runtime(frame, draw, true) } @(private) planned_executor_can_generated_2d_draw_shape :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { return planned_executor_can_generated_2d_draw_with_runtime(frame, draw, false) } @(private) planned_executor_can_generated_2d_draw_with_runtime :: proc( frame: ^Frame, draw: ^ir.Draw_Command, require_runtime_ready: bool, ) -> bool { if frame == nil || frame.runtime == nil || draw == nil { if require_runtime_ready { return false } if frame == nil || draw == nil { return false } } if draw.packet.target != INVALID_TARGET { return false } if draw.packet.instances.resource != INVALID_RESOURCE || draw.packet.instances.indirect != INVALID_RESOURCE { return false } instance_count := draw.packet.instances.count if instance_count == 0 { instance_count = draw.instance_count } if instance_count > 1 { return false } if draw.packet.clip != INVALID_CLIP { if clip, clip_ok := ir.get_clip(&frame.ir, draw.packet.clip); !clip_ok || clip.kind != .Rect { return false } } if require_runtime_ready && frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE { return false } switch draw.kind { case .Quad_Stream: if _, rect_ok := find_generated_rect_geometry(frame, draw.packet.geometry.resource); rect_ok { return true } textured_quad, textured_ok := find_generated_textured_quad_geometry( frame, draw.packet.geometry.resource, ) if !textured_ok { return false } if textured_quad.sampled_resource != INVALID_RESOURCE { return false } return textured_quad.texture_id != 0 case .Line_Stream: _, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource) return line_ok case .Vertex_Stream: _, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource) return circle_ok case .Glyph_Quad_Stream: glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource) if !glyph_ok || len(glyphs.quads) == 0 { return false } _, texture_ok := runtime_texture_id_from_material(frame, draw.packet.material) return texture_ok case .Raw, .Mesh, .Indexed_Mesh, .Point_Stream, .Indirect, .External: return false } return false } @(private) planned_executor_can_imported_mesh_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { if frame == nil || frame.runtime == nil || draw == nil { return false } if draw.kind != .Indexed_Mesh || draw.packet.geometry.kind != .Indexed_Mesh { return false } if draw.packet.instances.resource != INVALID_RESOURCE || draw.packet.instances.indirect != INVALID_RESOURCE { return false } if draw.packet.target != INVALID_TARGET { return false } if draw.packet.clip != INVALID_CLIP { if clip, clip_ok := ir.get_clip(&frame.ir, draw.packet.clip); !clip_ok || clip.kind != .Rect { return false } } if !frame.has_camera_3d { return false } instance_count := draw.packet.instances.count if instance_count == 0 { instance_count = draw.instance_count } if instance_count > 1 { return false } mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource) if !mesh_ok || mesh.id == 0 { return false } layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id) if !layout_ok { return false } material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material) if !material_ok { return false } variant := bk.layout_to_variant(&layout) pipeline := planned_imported_mesh_pipeline(frame, variant, material.double_sided) return pipeline != bk.NULL_PIPELINE } @(private) planned_imported_draw_resolver :: proc( user_data: rawptr, fir: ^ir.Frame_IR, command: ir.Command_Handle, draw: ^ir.Draw_Command, out: ^compiler.Imported_Draw_Binding, ) -> bool { _ = fir _ = command ctx := cast(^Planned_Imported_Draw_Context)user_data if ctx == nil || ctx.frame == nil || ctx.frame.runtime == nil || draw == nil || out == nil { return false } frame := ctx.frame if planned_draw_uses_generated_2d_resource(frame, draw) { return planned_generated_2d_draw_binding(frame, draw, out) } if !planned_executor_can_imported_mesh_draw(frame, draw) { return false } mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource) if !mesh_ok { return false } vb, ib, index_count, buffers_ok := resource.get_mesh_buffers( &frame.runtime.resource_state, mesh.id, ) if !buffers_ok || vb == bk.NULL_BUFFER || ib == bk.NULL_BUFFER || index_count <= 0 { return false } layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id) if !layout_ok { return false } material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material) if !material_ok { return false } variant := bk.layout_to_variant(&layout) pipeline := planned_imported_mesh_pipeline(frame, variant, material.double_sided) if pipeline == bk.NULL_PIPELINE { return false } model := draw.packet.transform if model == {} { model = matrix_identity() } if frame.lighting_3d_enabled { pc := renderer.Push_Constants_3D_Lit { model = model, base_color = material.base_color, metallic = material.metallic, roughness = material.roughness, reflectance = material.reflectance, emissive = material.emissive, } if size_of(pc) > compiler.IMPORTED_PUSH_CONSTANT_CAP { return false } mem.copy(&out.push_constant_bytes[0], &pc, size_of(pc)) out.push_constant_size = u32(size_of(pc)) out.descriptor_sets[0] = resource.get_texture_descriptor_set( &frame.runtime.resource_state, material.texture_id, ) out.descriptor_set_indices[0] = 0 out.descriptor_sets[1] = frame.runtime.render_state.light_state.ubo_descriptor_sets[frame.runtime.render_state.frame_ctx.frame_index] out.descriptor_set_indices[1] = 1 out.descriptor_set_count = 2 if renderer.variant_has_tangent(variant) { out.descriptor_sets[out.descriptor_set_count] = resource.get_texture_descriptor_set( &frame.runtime.resource_state, material.normal_map_id, ) out.descriptor_set_indices[out.descriptor_set_count] = 3 out.descriptor_set_count += 1 } } else { pc := renderer.Push_Constants_3D { proj_view = frame.runtime.render_state.draw_queue_3d.proj_view, model = model, } if size_of(pc) > compiler.IMPORTED_PUSH_CONSTANT_CAP { return false } mem.copy(&out.push_constant_bytes[0], &pc, size_of(pc)) out.push_constant_size = u32(size_of(pc)) out.descriptor_sets[0] = resource.get_texture_descriptor_set( &frame.runtime.resource_state, material.texture_id, ) out.descriptor_set_indices[0] = 0 out.descriptor_set_count = 1 } out.pipeline = pipeline out.push_constant_stages = {.Vertex} out.vertex_buffer = vb out.index_buffer = ib out.index_count = u32(index_count) out.instance_count = 1 out.indexed = true return true } @(private) planned_generated_2d_draw_binding :: proc( frame: ^Frame, draw: ^ir.Draw_Command, out: ^compiler.Imported_Draw_Binding, ) -> bool { if !planned_executor_can_generated_2d_draw(frame, draw) { return false } rs := &frame.runtime.render_state batch := &rs.batch_2d frame_index := batch.frame_index if frame_index >= bk.MAX_FRAMES_IN_FLIGHT { return false } base_index := len(batch.indices) texture_id: u32 = 0 switch draw.kind { case .Quad_Stream: if textured_quad, textured_ok := find_generated_textured_quad_geometry( frame, draw.packet.geometry.resource, ); textured_ok { texture_ok: bool texture_id, texture_ok = runtime_texture_id_from_textured_quad(frame, textured_quad, nil) if !texture_ok || texture_id == 0 { return false } if textured_quad.rotation == 0 { renderer.batch_add_textured_quad( batch, textured_quad.x, textured_quad.y, textured_quad.width, textured_quad.height, textured_quad.u0, textured_quad.v0, textured_quad.u1, textured_quad.v1, textured_quad.color, ) } else { renderer.batch_add_textured_quad_rotated( batch, textured_quad.x, textured_quad.y, textured_quad.width, textured_quad.height, textured_quad.u0, textured_quad.v0, textured_quad.u1, textured_quad.v1, textured_quad.rotation, textured_quad.color, ) } } else if rect, rect_ok := find_generated_rect_geometry( frame, draw.packet.geometry.resource, ); rect_ok { renderer.batch_add_rectangle(batch, rect.x, rect.y, rect.width, rect.height, rect.color) } else { return false } case .Line_Stream: line, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource) if !line_ok { return false } renderer.batch_add_line(batch, line.x0, line.y0, line.x1, line.y1, line.thickness, line.color) case .Vertex_Stream: circle, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource) if !circle_ok { return false } renderer.batch_add_circle(batch, circle.cx, circle.cy, circle.radius, circle.color) case .Glyph_Quad_Stream: glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource) if !glyph_ok || len(glyphs.quads) == 0 { return false } texture_ok: bool texture_id, texture_ok = runtime_texture_id_from_material(frame, draw.packet.material) if !texture_ok || texture_id == 0 { return false } for glyph in glyphs.quads { renderer.batch_add_textured_quad( batch, glyph.x, glyph.y, glyph.width, glyph.height, glyph.u0, glyph.v0, glyph.u1, glyph.v1, glyph.color, ) } case .Raw, .Mesh, .Indexed_Mesh, .Point_Stream, .Indirect, .External: return false } index_count := len(batch.indices) - base_index if index_count <= 0 { return false } vb_mapped := frame.runtime.backend.get_buffer_mapped(batch.vertex_buffers[frame_index]) ib_mapped := frame.runtime.backend.get_buffer_mapped(batch.index_buffers[frame_index]) if vb_mapped == nil || ib_mapped == nil { return false } mem.copy(vb_mapped, raw_data(batch.vertices), len(batch.vertices) * renderer.VERTEX_SIZE) mem.copy(ib_mapped, raw_data(batch.indices), len(batch.indices) * size_of(u32)) out.pipeline = batch.gfx_pipeline out.descriptor_sets[0] = resource.get_texture_descriptor_set(&frame.runtime.resource_state, texture_id) out.descriptor_set_indices[0] = 0 out.descriptor_set_count = 1 out.push_constant_stages = {.Vertex} out.push_constant_size = 64 mem.copy(&out.push_constant_bytes[0], &batch.proj_view, int(out.push_constant_size)) out.vertex_buffer = batch.vertex_buffers[frame_index] out.index_buffer = batch.index_buffers[frame_index] out.index_count = u32(index_count) out.first_index = u32(base_index) out.instance_count = 1 out.indexed = true return true } @(private) planned_imported_mesh_pipeline :: proc( frame: ^Frame, variant: bk.Layout_Variant, double_sided: bool, ) -> bk.Pipeline_Handle { if frame == nil || frame.runtime == nil { return bk.NULL_PIPELINE } rs := &frame.runtime.render_state if frame.lighting_3d_enabled { return renderer.select_pipeline( rs.pipeline_3d_lit, rs.pipeline_3d_lit_double_sided, variant, double_sided, ) } return renderer.select_pipeline( rs.pipeline_3d, rs.pipeline_3d_double_sided, variant, double_sided, ) } @(private) configure_runtime_mesh_phase :: proc(frame: ^Frame, frame_index: u32) { configure_runtime_camera(frame) configure_runtime_lighting(frame, frame_index) } @(private) configure_runtime_lighting :: proc(frame: ^Frame, frame_index: u32) { if frame == nil || frame.runtime == nil { return } rs := &frame.runtime.render_state renderer.clear_lights(&rs.light_state) rs.light_state.ambient_color = frame.ambient_light_3d if !frame.lighting_3d_enabled { return } for light in frame.lights_3d { _ = renderer.add_light( &rs.light_state, { type = u32(light.type), enabled = light.enabled, position = {light.position.x, light.position.y, light.position.z}, color = light.color, intensity = light.intensity, radius = light.radius, casts_shadow = light.casts_shadow, }, ) } renderer.update_light_ubo( &rs.light_state, &frame.runtime.backend, frame_index, rs.draw_queue_3d.proj_view, rs.camera_pos_3d, ) } @(private) submit_runtime_mixed_frame :: proc( frame: ^Frame, commands: []Runtime_Command, offscreen_exec: ^compiler.Execution_State, ) -> bool { rs := &frame.runtime.render_state clear_color := frame_present_clear_color(frame) renderer.begin_frame(rs, clear_color) if !rs.frame_active { return false } if runtime_commands_have_3d(commands) { configure_runtime_mesh_phase(frame, rs.frame_ctx.frame_index) } default_pipeline := rs.batch_2d.gfx_pipeline default_screen_proj := rs.batch_2d.screen_proj default_proj_view := rs.batch_2d.proj_view active_offscreen := false active_offscreen_target := INVALID_TARGET saw_present_draw := false for command in commands { if command.target.kind == .Offscreen { if command.kind != .Generated_2D || offscreen_exec == nil || saw_present_draw { return false } if !active_offscreen || active_offscreen_target != command.target.handle { if active_offscreen { renderer.flush_batch_2d(&rs.batch_2d) compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx) rs.render_pass_begun = false rs.batch_2d.gfx_pipeline = default_pipeline rs.batch_2d.screen_proj = default_screen_proj rs.batch_2d.proj_view = default_proj_view active_offscreen = false active_offscreen_target = INVALID_TARGET } render_pass, pass_ok := compiler.prepared_render_pass( offscreen_exec, command.target.pass, ) if !pass_ok { return false } pipeline, pipeline_ok := get_or_create_offscreen_2d_pipeline(render_pass) if !pipeline_ok { return false } if !compiler.begin_prepared_pass( offscreen_exec, rs.frame_ctx, command.target.pass, ) { return false } rs.render_pass_begun = true rs.batch_2d.gfx_pipeline = pipeline renderer.update_screen_projection( &rs.batch_2d, f32(command.target.width), f32(command.target.height), ) active_offscreen = true active_offscreen_target = command.target.handle } } else { if active_offscreen { renderer.flush_batch_2d(&rs.batch_2d) compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx) rs.render_pass_begun = false rs.batch_2d.gfx_pipeline = default_pipeline rs.batch_2d.screen_proj = default_screen_proj rs.batch_2d.proj_view = default_proj_view active_offscreen = false active_offscreen_target = INVALID_TARGET } saw_present_draw = true } switch command.kind { case .Dispatch: frame.runtime.render_state.profile.compute_dispatches += 1 dispatch_command := command.dispatch.command renderer.execute_compute_dispatch(rs, &frame.runtime.resource_state, &dispatch_command) case .Generated_2D: submit_runtime_billboards(frame) submit_runtime_generated_2d_command(rs, command.generated_2d) case .Mesh: renderer.flush_batch_2d(&rs.batch_2d) submit_runtime_billboards(frame) submit_runtime_mesh_command(frame, command.mesh) case .Billboard_3D: renderer.flush_batch_2d(&rs.batch_2d) submit_runtime_billboard_command(frame, command.billboard) case .Quad_3D: renderer.flush_batch_2d(&rs.batch_2d) submit_runtime_quad_3d_command(frame, command.quad_3d) case .Raw: renderer.flush_batch_2d(&rs.batch_2d) submit_runtime_billboards(frame) if !submit_runtime_raw_command(frame, command.raw) { return false } } } if active_offscreen { renderer.flush_batch_2d(&rs.batch_2d) compiler.end_prepared_pass(offscreen_exec, rs.frame_ctx) rs.render_pass_begun = false rs.batch_2d.gfx_pipeline = default_pipeline rs.batch_2d.screen_proj = default_screen_proj rs.batch_2d.proj_view = default_proj_view active_offscreen = false active_offscreen_target = INVALID_TARGET } submit_runtime_billboards(frame) _ = renderer.end_frame(rs) return true } @(private) runtime_commands_have_3d :: proc(commands: []Runtime_Command) -> bool { for command in commands { if command.kind == .Mesh || command.kind == .Billboard_3D || command.kind == .Quad_3D { return true } } return false } @(private) submit_runtime_generated_2d_command :: proc( rs: ^renderer.Renderer_State, draw: Runtime_Generated_2D_Draw, ) { switch draw.kind { case .Rect: renderer.batch_set_texture(&rs.batch_2d, 0) rect := draw.rect renderer.batch_add_rectangle( &rs.batch_2d, rect.x, rect.y, rect.width, rect.height, rect.color, ) case .Textured_Quad: quad := draw.textured_quad renderer.batch_set_texture(&rs.batch_2d, quad.texture_id) if quad.rotation == 0 { renderer.batch_add_textured_quad( &rs.batch_2d, quad.x, quad.y, quad.width, quad.height, quad.u0, quad.v0, quad.u1, quad.v1, quad.color, ) } else { renderer.batch_add_textured_quad_rotated( &rs.batch_2d, quad.x, quad.y, quad.width, quad.height, quad.u0, quad.v0, quad.u1, quad.v1, quad.rotation, quad.color, ) } case .Line: renderer.batch_set_texture(&rs.batch_2d, 0) line := draw.line renderer.batch_add_line( &rs.batch_2d, line.x0, line.y0, line.x1, line.y1, line.thickness, line.color, ) case .Circle: renderer.batch_set_texture(&rs.batch_2d, 0) circle := draw.circle renderer.batch_add_circle(&rs.batch_2d, circle.cx, circle.cy, circle.radius, circle.color) case .Glyphs: renderer.batch_set_texture(&rs.batch_2d, draw.texture_id) for glyph in draw.glyphs.quads { renderer.batch_add_textured_quad( &rs.batch_2d, glyph.x, glyph.y, glyph.width, glyph.height, glyph.u0, glyph.v0, glyph.u1, glyph.v1, glyph.color, ) } } } @(private) submit_runtime_mesh_command :: proc(frame: ^Frame, draw: Runtime_Mesh_Draw) { rs := &frame.runtime.render_state renderer.push_draw_command_3d( &rs.draw_queue_3d, draw.mesh.id, draw.texture_id, draw.layout_variant, draw.model, draw.double_sided, draw.base_color, draw.metallic, draw.roughness, draw.reflectance, draw.emissive, draw.normal_map_id, ) renderer.ensure_render_pass(rs) if frame.lighting_3d_enabled && runtime_lit_pipeline_available(rs, draw.layout_variant, draw.double_sided) { renderer.flush_draw_queue_3d_lit( &rs.draw_queue_3d, &frame.runtime.backend, rs.frame_ctx, rs.pipeline_3d_lit, rs.pipeline_3d_lit_double_sided, rs.light_state.ubo_descriptor_sets[rs.frame_ctx.frame_index], ) } else { renderer.flush_draw_queue_3d( &rs.draw_queue_3d, &frame.runtime.backend, rs.frame_ctx, rs.pipeline_3d, rs.pipeline_3d_double_sided, ) } rs.profile.queue3d_flushes += 1 rs.profile.draw_indexed_calls += 1 rs.profile.push_constants += 1 } @(private) runtime_lit_pipeline_available :: proc( rs: ^renderer.Renderer_State, variant: bk.Layout_Variant, double_sided: bool, ) -> bool { if rs == nil { return false } if double_sided { return rs.pipeline_3d_lit_double_sided[variant] != bk.NULL_PIPELINE } return rs.pipeline_3d_lit[variant] != bk.NULL_PIPELINE } @(private) submit_runtime_billboard_command :: proc(frame: ^Frame, draw: Runtime_Billboard_Draw) { rs := &frame.runtime.render_state _ = renderer.add_billboard_3d( &rs.billboard_3d_batch, {draw.position.x, draw.position.y, draw.position.z}, draw.size, draw.color, rs.camera_right, rs.camera_up, ) } @(private) submit_runtime_quad_3d_command :: proc(frame: ^Frame, draw: Runtime_Quad_3D_Draw) { rs := &frame.runtime.render_state _ = renderer.add_quad_3d(&rs.billboard_3d_batch, draw.corners, draw.normal, draw.color) } @(private) submit_runtime_billboards :: proc(frame: ^Frame) { rs := &frame.runtime.render_state renderer.flush_billboard_3d_batch( &rs.billboard_3d_batch, &frame.runtime.backend, rs.frame_ctx, rs.pipeline_3d_double_sided[bk.Layout_Variant.PNUC], rs.frame_ctx.frame_index, rs.draw_queue_3d.proj_view, &frame.runtime.resource_state, ) } @(private) submit_runtime_raw_command :: proc(frame: ^Frame, draw: Runtime_Raw_Draw) -> bool { if draw.callback == nil { return false } rs := &frame.runtime.render_state renderer.ensure_render_pass(rs) return draw.callback(draw.user_data, &frame.runtime.backend, rs.frame_ctx) } frame_runtime_mode :: proc(frame: ^Frame) -> (Runtime_Frame_Mode, bool) { plan := frame_runtime_plan(frame) defer destroy_frame_runtime_plan(&plan) commands := make( [dynamic]Runtime_Command, 0, ir.command_count(&frame.ir), context.temp_allocator, ) defer delete(commands) ir.reset_diagnostics(&frame.diagnostics) return collect_runtime_commands(frame, plan[:], &commands, diagnostics = &frame.diagnostics) } frame_planned_coverage_path :: proc(frame: ^Frame) -> Frame_Planned_Coverage_Path { if frame == nil || ir.command_count(&frame.ir) == 0 { return .Empty } for i in 0 ..< len(frame.ir.commands) { handle := ir.Command_Handle(i + 1) record := frame.ir.commands[i] if record.kind != .Draw { return .Migration } draw, draw_ok := ir.get_draw(&frame.ir, handle) if !draw_ok || !planned_executor_can_draw_shape(frame, draw) { return .Migration } } return .Planned } @(private) planned_executor_can_draw_shape :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> bool { switch draw.kind { case .Mesh, .Indexed_Mesh, .Vertex_Stream, .Quad_Stream, .Line_Stream, .Point_Stream, .Glyph_Quad_Stream, .Indirect: case .Raw, .External: return false } if draw.packet.geometry.resource == INVALID_RESOURCE { return false } if planned_draw_uses_generated_2d_resource(frame, draw) { return planned_executor_can_generated_2d_draw_shape(frame, draw) } if planned_draw_uses_imported_resource(frame, draw) { return frame != nil && frame.runtime != nil && planned_executor_can_imported_mesh_draw(frame, draw) } pipeline := draw.pipeline if pipeline == ir.INVALID_PIPELINE { material, material_ok := ir.get_material(&frame.ir, draw.packet.material) if !material_ok { return false } pipeline = material.pipeline } pipeline_desc, pipeline_ok := ir.get_pipeline(&frame.ir, pipeline) if !pipeline_ok || pipeline_desc.kind != .Graphics { return false } if draw.kind == .Indirect || draw.packet.instances.indirect != INVALID_RESOURCE { return planned_executor_can_indirect_draw(draw) && planned_executor_can_instance_draw(draw, pipeline_desc) } if draw.packet.instances.resource != INVALID_RESOURCE { return planned_executor_can_instance_draw(draw, pipeline_desc) } return true } @(private) collect_runtime_commands :: proc( frame: ^Frame, plan: []Planned_Command, out: ^[dynamic]Runtime_Command, offscreen_exec: ^compiler.Execution_State = nil, diagnostics: ^ir.Diagnostic_List = nil, ) -> ( Runtime_Frame_Mode, bool, ) { mode := Runtime_Frame_Mode.Empty saw_draw := false for command in plan { runtime_command: Runtime_Command next := Runtime_Frame_Mode.Empty command_ok := false diagnostic_count := 0 if diagnostics != nil { diagnostic_count = len(diagnostics.items) } switch command.record.kind { case .Draw: draw, ok := ir.get_draw(&frame.ir, command.handle) if !ok { runtime_add_error( diagnostics, command.handle, .Invalid_Command, "draw command payload is missing", ) return .Empty, false } runtime_command, next, command_ok = runtime_command_from_draw( frame, draw, command.handle, offscreen_exec, diagnostics, ) saw_draw = true case .Dispatch: if saw_draw { runtime_add_error( diagnostics, command.handle, .Unsupported, "dispatch after draw is not supported by the migration runtime", ) return .Empty, false } dispatch, ok := ir.get_dispatch(&frame.ir, command.handle) if !ok { runtime_add_error( diagnostics, command.handle, .Invalid_Command, "dispatch command payload is missing", ) return .Empty, false } runtime_command, next, command_ok = runtime_command_from_dispatch( frame, dispatch, command.handle, diagnostics, ) } if !command_ok { if diagnostics != nil && len(diagnostics.items) == diagnostic_count { runtime_add_error( diagnostics, command.handle, .Unsupported, "runtime lowering rejected command", ) } return .Empty, false } if mode == .Empty { mode = next } else if mode != next { mode = .Mixed } append(out, runtime_command) } return mode, mode != .Empty } @(private) runtime_add_error :: proc( diagnostics: ^ir.Diagnostic_List, command: Command_Handle, code: ir.Diagnostic_Code, message: string, ) { if diagnostics != nil { ir.add_command_error(diagnostics, code, command, message) } } @(private) runtime_command_error :: proc( diagnostics: ^ir.Diagnostic_List, command: Command_Handle, code: ir.Diagnostic_Code, message: string, ) -> ( Runtime_Command, Runtime_Frame_Mode, bool, ) { runtime_add_error(diagnostics, command, code, message) return {}, .Empty, false } @(private) runtime_command_from_dispatch :: proc( frame: ^Frame, dispatch: ^ir.Dispatch_Command, command: Command_Handle, diagnostics: ^ir.Diagnostic_List = nil, ) -> ( Runtime_Command, Runtime_Frame_Mode, bool, ) { if dispatch.shader_id == 0 || dispatch.groups[0] == 0 || dispatch.groups[1] == 0 || dispatch.groups[2] == 0 { return runtime_command_error( diagnostics, command, .Invalid_Command, "dispatch requires shader id and non-zero workgroup dimensions", ) } count := min(int(dispatch.storage_binding_count), ir.MAX_COMPUTE_BINDINGS) runtime_dispatch := renderer.Runtime_Compute_Dispatch { shader_id = dispatch.shader_id, groups = dispatch.groups, push_constants = dispatch.push_constants, barrier_after = dispatch.barrier_after, storage_binding_count = u8(count), } if frame.runtime != nil { if !bk.supports(frame.runtime.backend.capabilities, .Compute_Dispatch) { return runtime_command_error( diagnostics, command, .Unsupported, "backend does not support compute dispatch", ) } if count > 0 && !bk.supports(frame.runtime.backend.capabilities, .Storage_Buffers) { return runtime_command_error( diagnostics, command, .Unsupported, "backend does not support storage buffers", ) } if _, ok := resource.get_compute_shader(&frame.runtime.resource_state, dispatch.shader_id); !ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "compute shader id is not loaded", ) } } for i in 0 ..< count { binding := dispatch.storage_bindings[i] if binding.type != .Storage_Buffer || binding.resource == INVALID_RESOURCE { return runtime_command_error( diagnostics, command, .Invalid_Resource, "dispatch storage binding is invalid", ) } buffer, buffer_ok := find_imported_storage_buffer(frame, binding.resource) if !buffer_ok || buffer.id == 0 || buffer.size <= 0 { return runtime_command_error( diagnostics, command, .Invalid_Resource, "dispatch storage binding is missing imported buffer metadata", ) } if frame.runtime != nil { if _, ok := resource.get_storage_buffer(&frame.runtime.resource_state, buffer.id); !ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "storage buffer id is not loaded", ) } } runtime_dispatch.storage_bindings[i] = { binding = binding.binding, storage_buffer_id = buffer.id, size = binding.size, } } return { kind = .Dispatch, target = runtime_present_target(), dispatch = {command = runtime_dispatch}, }, .Dispatches, true } @(private) runtime_texture_id_from_textured_quad :: proc( frame: ^Frame, quad: Generated_Textured_Quad_Geometry, offscreen_exec: ^compiler.Execution_State, ) -> ( u32, bool, ) { if quad.sampled_resource == INVALID_RESOURCE { return quad.texture_id, quad.texture_id != 0 } resource_desc, resource_ok := ir.get_resource(&frame.ir, quad.sampled_resource) if !resource_ok || resource_desc.kind != .Texture { return 0, false } if frame.runtime == nil { return 0, true } if offscreen_exec == nil { return 0, false } prepared_texture, prepared_ok := compiler.prepared_texture( offscreen_exec, quad.sampled_resource, ) if !prepared_ok { return 0, false } return get_or_import_offscreen_sampled_texture( frame.runtime, prepared_texture, resource_desc.texture.width, resource_desc.texture.height, ) } @(private) runtime_command_from_draw :: proc( frame: ^Frame, draw: ^ir.Draw_Command, command: Command_Handle, offscreen_exec: ^compiler.Execution_State = nil, diagnostics: ^ir.Diagnostic_List = nil, ) -> ( Runtime_Command, Runtime_Frame_Mode, bool, ) { // Migration bridge: planned IR must become the authoritative executor, not more per-feature branches here. target, target_ok := runtime_target_from_draw(frame, draw) if !target_ok { return runtime_command_error( diagnostics, command, .Invalid_Handle, "draw target is invalid", ) } instance_count := draw.packet.instances.count if instance_count == 0 { instance_count = 1 } if instance_count != 1 { return runtime_command_error( diagnostics, command, .Unsupported, "migration runtime does not support instanced draw lowering yet", ) } #partial switch draw.packet.geometry.kind { case .Raw: if target.kind == .Offscreen { return runtime_command_error( diagnostics, command, .Unsupported, "migration runtime does not support raw offscreen draws", ) } raw_draw, raw_ok := find_generated_raw_draw(frame, draw.packet.geometry.resource) if !raw_ok || raw_draw.callback == nil { return runtime_command_error( diagnostics, command, .Invalid_Resource, "raw draw callback is missing", ) } return { kind = .Raw, target = target, raw = {callback = raw_draw.callback, user_data = raw_draw.user_data}, }, .Raw, true case .Quad_Stream: if frame.runtime != nil && frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "2D runtime pipeline is not initialized", ) } textured_quad, textured_quad_ok := find_generated_textured_quad_geometry( frame, draw.packet.geometry.resource, ) if textured_quad_ok { texture_id, texture_ok := runtime_texture_id_from_textured_quad( frame, textured_quad, offscreen_exec, ) if !texture_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "textured quad texture is not available", ) } textured_quad.texture_id = texture_id return { kind = .Generated_2D, target = target, generated_2d = {kind = .Textured_Quad, textured_quad = textured_quad}, }, .Generated_2D, true } rect, rect_ok := find_generated_rect_geometry(frame, draw.packet.geometry.resource) if !rect_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "quad stream geometry is missing", ) } return {kind = .Generated_2D, target = target, generated_2d = {kind = .Rect, rect = rect}}, .Generated_2D, true case .Line_Stream: if frame.runtime != nil && frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "2D runtime pipeline is not initialized", ) } line, line_ok := find_generated_line_geometry(frame, draw.packet.geometry.resource) if !line_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "line stream geometry is missing", ) } return {kind = .Generated_2D, target = target, generated_2d = {kind = .Line, line = line}}, .Generated_2D, true case .Vertex_Stream: quad_3d, quad_3d_ok := find_generated_quad_3d_geometry( frame, draw.packet.geometry.resource, ) if quad_3d_ok { if target.kind == .Offscreen { return runtime_command_error( diagnostics, command, .Unsupported, "migration runtime does not support 3D quad offscreen draws", ) } if !frame.has_camera_3d { return runtime_command_error( diagnostics, command, .Invalid_Command, "3D quad draw requires a camera", ) } if frame.runtime != nil { pipeline := renderer.select_pipeline( frame.runtime.render_state.pipeline_3d, frame.runtime.render_state.pipeline_3d_double_sided, bk.Layout_Variant.PNUC, true, ) if pipeline == {} { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "3D quad pipeline is not initialized", ) } } return { kind = .Quad_3D, target = target, quad_3d = { corners = quad_3d.corners, normal = quad_3d.normal, color = quad_3d.color, }, }, .Billboards, true } if frame.runtime != nil && frame.runtime.render_state.batch_2d.gfx_pipeline == bk.NULL_PIPELINE { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "2D runtime pipeline is not initialized", ) } circle, circle_ok := find_generated_circle_geometry(frame, draw.packet.geometry.resource) if !circle_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "vertex stream geometry is missing", ) } return { kind = .Generated_2D, target = target, generated_2d = {kind = .Circle, circle = circle}, }, .Generated_2D, true case .Point_Stream: if target.kind == .Offscreen { return runtime_command_error( diagnostics, command, .Unsupported, "migration runtime does not support billboard offscreen draws", ) } if !frame.has_camera_3d { return runtime_command_error( diagnostics, command, .Invalid_Command, "billboard draw requires a camera", ) } billboard, billboard_ok := find_generated_billboard_geometry( frame, draw.packet.geometry.resource, ) if !billboard_ok || billboard.size <= 0 { return runtime_command_error( diagnostics, command, .Invalid_Resource, "point stream billboard geometry is missing", ) } if frame.runtime != nil { pipeline := renderer.select_pipeline( frame.runtime.render_state.pipeline_3d, frame.runtime.render_state.pipeline_3d_double_sided, bk.Layout_Variant.PNUC, true, ) if pipeline == {} { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "billboard pipeline is not initialized", ) } } return { kind = .Billboard_3D, target = target, billboard = { position = billboard.position, size = billboard.size, color = billboard.color, }, }, .Billboards, true case .Glyph_Quad_Stream: glyphs, glyph_ok := find_generated_glyph_geometry(frame, draw.packet.geometry.resource) if !glyph_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "glyph stream geometry is missing", ) } texture_id, texture_ok := runtime_texture_id_from_material(frame, draw.packet.material) if !texture_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "glyph material texture is not available", ) } return { kind = .Generated_2D, target = target, generated_2d = {kind = .Glyphs, glyphs = glyphs, texture_id = texture_id}, }, .Generated_2D, true case .Indexed_Mesh: if target.kind == .Offscreen { return runtime_command_error( diagnostics, command, .Unsupported, "migration runtime does not support mesh offscreen draws", ) } if !frame.has_camera_3d { return runtime_command_error( diagnostics, command, .Invalid_Command, "mesh draw requires a camera", ) } mesh, mesh_ok := find_imported_mesh_geometry(frame, draw.packet.geometry.resource) if !mesh_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "indexed mesh geometry is missing", ) } material, material_ok := runtime_mesh_material_from_handle(frame, draw.packet.material) if !material_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "mesh material is invalid", ) } layout_variant: bk.Layout_Variant if frame.runtime != nil { layout, layout_ok := resource.get_mesh_layout(&frame.runtime.resource_state, mesh.id) if !layout_ok { return runtime_command_error( diagnostics, command, .Invalid_Resource, "mesh layout is not loaded", ) } layout_variant = bk.layout_to_variant(&layout) pipeline := renderer.select_pipeline( frame.runtime.render_state.pipeline_3d, frame.runtime.render_state.pipeline_3d_double_sided, layout_variant, material.double_sided, ) if pipeline == {} { return runtime_command_error( diagnostics, command, .Invalid_Pipeline, "mesh pipeline is not initialized", ) } } model := draw.packet.transform if model == {} { model = matrix_identity() } return { kind = .Mesh, target = target, mesh = { mesh = mesh, layout_variant = layout_variant, model = model, texture_id = material.texture_id, normal_map_id = material.normal_map_id, double_sided = material.double_sided, base_color = material.base_color, metallic = material.metallic, roughness = material.roughness, reflectance = material.reflectance, emissive = material.emissive, }, }, .Meshes, true case: return runtime_command_error( diagnostics, command, .Unsupported, "draw geometry kind is not supported by the migration runtime", ) } return runtime_command_error( diagnostics, command, .Unsupported, "draw geometry kind is not supported by the migration runtime", ) } @(private) runtime_present_target :: proc() -> Runtime_Target { return {handle = INVALID_TARGET, kind = .Present} } @(private) runtime_target_from_draw :: proc(frame: ^Frame, draw: ^ir.Draw_Command) -> (Runtime_Target, bool) { if draw.packet.target == INVALID_TARGET { return runtime_present_target(), true } target, target_ok := ir.get_target(&frame.ir, draw.packet.target) if !target_ok { return {}, false } if target.kind == .Present { return {handle = draw.packet.target, kind = .Present, pass = target.pass}, true } if target.kind != .Offscreen { return {}, false } return { handle = draw.packet.target, kind = .Offscreen, pass = target.pass, width = target.width, height = target.height, }, true } @(private) configure_runtime_camera :: proc(frame: ^Frame) { camera := frame.camera_3d rs := &frame.runtime.render_state view := renderer.look_at(camera.position, camera.target, camera.up) extent := frame.runtime.backend.get_extent() aspect := f32(extent.width) / f32(extent.height) near := camera.near if camera.near > 0 else 0.1 far := camera.far if camera.far > 0 else 100.0 fovy := camera.fovy if camera.fovy > 0 else 60.0 proj := renderer.perspective(fovy, aspect, near, far) rs.draw_queue_3d.proj_view = proj * view rs.camera_pos_3d = {camera.position.x, camera.position.y, camera.position.z} rs.camera_target_3d = {camera.target.x, camera.target.y, camera.target.z} rs.camera_right = {view[0, 0], view[0, 1], view[0, 2]} rs.camera_up = {view[1, 0], view[1, 1], view[1, 2]} renderer.reset_draw_queue_3d(&rs.draw_queue_3d) } @(private) frame_present_clear_color :: proc(frame: ^Frame) -> Color { for target in frame.ir.targets { if target.kind == .Present { return target.clear_color } } return BLACK }