package gpu_tests import gpu ".." import bk "../backend" import particles "../particles" import ir "../render_ir" import renderer "../renderer" import "core:testing" test_camera_3d :: proc() -> gpu.Camera3D { return { position = {0, 0, 5}, target = {0, 0, 0}, up = {0, 1, 0}, fovy = 60, near = 0.1, far = 100, } } @(test) test_frame_api_builds_target_layer_and_packets :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) testing.expect(t, !gpu.context_is_runtime_backed(ctx)) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) testing.expect(t, !gpu.frame_is_runtime_backed(&frame)) target := gpu.begin_target(&frame, gpu.present_target(ctx)) testing.expect(t, target != gpu.INVALID_TARGET) layer := gpu.push_layer(&frame, {name = "ui", sort_base = 10_000, order = .Strict}) testing.expect(t, layer != gpu.INVALID_LAYER) rect := gpu.draw_rect( &frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE, sort_key = 7}, ) text := gpu.draw_text_run( &frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, { x = 16, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, }, sort_key = 8, }, ) testing.expect(t, rect != gpu.INVALID_COMMAND) testing.expect(t, text != gpu.INVALID_COMMAND) fir := gpu.frame_ir(&frame) testing.expect(t, fir != nil) testing.expect_value(t, len(fir.targets), 1) testing.expect_value(t, len(fir.layers), 1) testing.expect_value(t, len(fir.resources), 2) testing.expect_value(t, len(fir.draw_commands), 2) testing.expect(t, fir.draw_commands[0].kind == .Quad_Stream) testing.expect(t, fir.draw_commands[0].packet.target == target) testing.expect(t, fir.draw_commands[0].packet.layer == layer) testing.expect(t, fir.draw_commands[0].packet.geometry.resource != gpu.INVALID_RESOURCE) testing.expect(t, fir.resources[0].kind == .Buffer) testing.expect(t, fir.resources[0].lifetime == .Transient) testing.expect(t, .Vertex in fir.resources[0].buffer.usage) testing.expect_value(t, fir.draw_commands[0].packet.sequence, u64(1)) testing.expect(t, fir.draw_commands[1].kind == .Glyph_Quad_Stream) testing.expect(t, fir.draw_commands[1].packet.geometry.resource != gpu.INVALID_RESOURCE) testing.expect_value(t, fir.draw_commands[1].packet.geometry.vertex_count, u32(12)) testing.expect(t, fir.resources[1].kind == .Buffer) testing.expect(t, fir.resources[1].lifetime == .Transient) testing.expect_value(t, fir.draw_commands[1].packet.sequence, u64(2)) testing.expect(t, gpu.submit(&frame)) } @(test) test_frame_api_draw_line_emits_generic_line_stream :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) line := gpu.draw_line_frame( &frame, {x0 = 10, y0 = 20, x1 = 100, y1 = 120, thickness = 3, color = gpu.YELLOW, sort_key = 5}, ) fir := gpu.frame_ir(&frame) testing.expect(t, line != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Line_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.resource != gpu.INVALID_RESOURCE) testing.expect_value(t, fir.draw_commands[0].packet.geometry.vertex_count, u32(6)) testing.expect_value(t, fir.draw_commands[0].packet.sequence, u64(1)) testing.expect(t, fir.resources[0].kind == .Buffer) testing.expect(t, fir.resources[0].lifetime == .Transient) testing.expect(t, .Vertex in fir.resources[0].buffer.usage) } @(test) test_frame_api_draw_circle_emits_generic_vertex_stream :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) circle := gpu.draw_circle_frame( &frame, {cx = 100, cy = 120, radius = 40, color = gpu.YELLOW, sort_key = 5}, ) fir := gpu.frame_ir(&frame) testing.expect(t, circle != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Vertex_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.kind == .Vertex_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.resource != gpu.INVALID_RESOURCE) testing.expect_value( t, fir.draw_commands[0].packet.geometry.vertex_count, u32(1 + renderer.CIRCLE_SEGMENTS), ) testing.expect_value(t, fir.draw_commands[0].packet.sequence, u64(1)) testing.expect(t, fir.resources[0].kind == .Buffer) testing.expect(t, fir.resources[0].lifetime == .Transient) testing.expect(t, .Vertex in fir.resources[0].buffer.usage) } @(test) test_frame_api_draw_circle_rejects_invalid_radius_without_resource :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) circle := gpu.draw_circle_frame(&frame, {cx = 100, cy = 120, radius = 0, color = gpu.YELLOW}) fir := gpu.frame_ir(&frame) testing.expect(t, circle == gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 0) testing.expect_value(t, len(fir.draw_commands), 0) } @(test) test_frame_api_draw_billboard_emits_generic_point_stream :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) billboard := gpu.draw_billboard_frame( &frame, {position = {1, 2, 3}, size = 2, color = gpu.YELLOW, sort_key = 5}, ) fir := gpu.frame_ir(&frame) testing.expect(t, billboard != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Point_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.kind == .Point_Stream) testing.expect_value(t, fir.draw_commands[0].packet.geometry.vertex_count, u32(1)) } @(test) test_frame_api_billboard_requires_camera_for_runtime :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.draw_billboard_frame(&frame, {position = {0, 1, 0}, size = 1, color = gpu.WHITE}) _, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, !ok) } @(test) test_frame_runtime_mode_accepts_billboard_only_with_camera :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) gpu.set_camera_3d(&frame, test_camera_3d()) _ = gpu.draw_billboard_frame(&frame, {position = {0, 1, 0}, size = 1, color = gpu.WHITE}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Billboards) } @(test) test_frame_api_draw_quad_3d_emits_generic_vertex_stream :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) quad := gpu.draw_quad_3d_frame( &frame, { corners = {{-1, 0, -1}, {1, 0, -1}, {1, 0, 1}, {-1, 0, 1}}, normal = {0, 1, 0}, color = {0, 0, 0, 0.35}, sort_key = 5, }, ) fir := gpu.frame_ir(&frame) testing.expect(t, quad != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Vertex_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.kind == .Vertex_Stream) testing.expect_value(t, fir.draw_commands[0].packet.geometry.vertex_count, u32(4)) testing.expect_value(t, fir.draw_commands[0].packet.geometry.index_count, u32(6)) } @(test) test_frame_api_quad_3d_requires_camera_for_runtime :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.draw_quad_3d_frame( &frame, {corners = {{-1, 0, -1}, {1, 0, -1}, {1, 0, 1}, {-1, 0, 1}}, color = {0, 0, 0, 0.35}}, ) _, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, !ok) } @(test) test_frame_runtime_mode_accepts_quad_3d_with_camera :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) gpu.set_camera_3d(&frame, test_camera_3d()) _ = gpu.draw_quad_3d_frame( &frame, {corners = {{-1, 0, -1}, {1, 0, -1}, {1, 0, 1}, {-1, 0, 1}}, color = {0, 0, 0, 0.35}}, ) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Billboards) } @(test) test_particles_3d_producer_draws_frame_billboards :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) config := particles.Config_3D { max_particles = 4, lifetime = {1, 1}, speed = {0, 0}, direction = {0, 1, 0}, spread = 0, start_color = {1, 0, 0, 1}, end_color = {1, 0, 0, 1}, start_size = 2, end_size = 2, gravity = {0, 0, 0}, } system, ok := particles.init_3d(config) testing.expect(t, ok) defer particles.destroy_3d(&system) particles.emit_3d(&system, {10, 20, 30}, 1) particles.draw_3d(&frame, &system) fir := gpu.frame_ir(&frame) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Point_Stream) } @(test) test_frame_api_draw_texture_quad_emits_generic_quad_stream :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) texture := gpu.Texture { id = 11, width = 64, height = 32, } cmd := gpu.draw_texture_quad( &frame, { texture = texture, x = 12, y = 24, u0 = 0.25, v0 = 0.75, u1 = 0.5, v1 = 0.125, rotation = 15, color = gpu.WHITE, sort_key = 3, }, ) fir := gpu.frame_ir(&frame) testing.expect(t, cmd != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect_value(t, len(fir.draw_commands), 1) testing.expect(t, fir.draw_commands[0].kind == .Quad_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.kind == .Quad_Stream) testing.expect(t, fir.draw_commands[0].packet.geometry.resource != gpu.INVALID_RESOURCE) testing.expect_value(t, fir.draw_commands[0].packet.geometry.vertex_count, u32(6)) quad, quad_ok := gpu.find_generated_textured_quad_geometry( &frame, fir.draw_commands[0].packet.geometry.resource, ) testing.expect(t, quad_ok) testing.expect_value(t, quad.texture_id, u32(11)) testing.expect_value(t, quad.width, f32(64)) testing.expect_value(t, quad.height, f32(32)) testing.expect_value(t, quad.u0, f32(0.25)) testing.expect_value(t, quad.v0, f32(0.75)) testing.expect_value(t, quad.u1, f32(0.5)) testing.expect_value(t, quad.v1, f32(0.125)) testing.expect_value(t, quad.rotation, f32(15)) testing.expect(t, quad.color == gpu.WHITE) } @(test) test_frame_api_draw_texture_quad_rejects_invalid_texture_without_resource :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) cmd := gpu.draw_texture_quad( &frame, {texture = {}, x = 12, y = 24, width = 64, height = 32, color = gpu.WHITE}, ) fir := gpu.frame_ir(&frame) testing.expect(t, cmd == gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 0) testing.expect_value(t, len(fir.draw_commands), 0) } @(test) test_frame_api_texture_material_rejects_invalid_texture_without_material :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) material := gpu.add_texture_material(&frame, {}, {name = "invalid-atlas"}) fir := gpu.frame_ir(&frame) testing.expect(t, material == gpu.INVALID_MATERIAL) testing.expect_value(t, len(fir.materials), 0) } @(test) test_frame_runtime_mode_accepts_glyphs_with_texture_material :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) material := gpu.add_texture_material( &frame, {id = 22, width = 128, height = 128}, {name = "glyph-atlas", blend_mode = .Alpha}, ) text := gpu.draw_text_run( &frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 8, height = 12, u0 = 0.125, v0 = 0.25, u1 = 0.1875, v1 = 0.34375, color = gpu.WHITE, }, { x = 18, y = 20, width = 8, height = 12, u0 = 0.1875, v0 = 0.25, u1 = 0.25, v1 = 0.34375, color = gpu.YELLOW, }, }, material = material, }, ) fir := gpu.frame_ir(&frame) testing.expect(t, material != gpu.INVALID_MATERIAL) testing.expect(t, text != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.materials), 1) testing.expect_value(t, fir.materials[0].texture_id, u32(22)) testing.expect(t, fir.draw_commands[0].packet.material == material) testing.expect_value(t, fir.draw_commands[0].packet.geometry.vertex_count, u32(12)) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Generated_2D) } @(test) test_frame_runtime_mode_accepts_generated_textured_quads_with_shapes :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.draw_texture_quad( &frame, {texture = {id = 11, width = 64, height = 32}, x = 10, y = 20, color = gpu.WHITE}, ) _ = gpu.draw_rect(&frame, {x = 100, y = 20, width = 30, height = 40, color = gpu.RED}) _ = gpu.draw_circle_frame(&frame, {cx = 200, cy = 120, radius = 30, color = gpu.YELLOW}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Generated_2D) } @(test) test_frame_runtime_mode_accepts_generated_2d_rects_and_lines :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.draw_rect(&frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}) _ = gpu.draw_line_frame(&frame, {x0 = 0, y0 = 0, x1 = 100, y1 = 100, color = gpu.RED}) _ = gpu.draw_circle_frame(&frame, {cx = 200, cy = 120, radius = 30, color = gpu.YELLOW}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Generated_2D) } @(test) test_frame_planned_coverage_marks_generated_shape_slice_planned :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.draw_rect(&frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}) _ = gpu.draw_line_frame(&frame, {x0 = 0, y0 = 0, x1 = 100, y1 = 100, color = gpu.RED}) _ = gpu.draw_circle_frame(&frame, {cx = 200, cy = 120, radius = 30, color = gpu.YELLOW}) testing.expect(t, gpu.frame_planned_coverage_path(&frame) == .Planned) } @(test) test_frame_planned_coverage_marks_generated_texture_and_glyph_slice_planned :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) textured_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&textured_frame) _ = gpu.draw_texture_quad( &textured_frame, {texture = {id = 11, width = 64, height = 32}, x = 10, y = 20, color = gpu.WHITE}, ) testing.expect(t, gpu.frame_planned_coverage_path(&textured_frame) == .Planned) glyph_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&glyph_frame) material := gpu.add_texture_material( &glyph_frame, {id = 12, width = 64, height = 32}, ) _ = gpu.draw_text_run( &glyph_frame, { material = material, glyphs = []gpu.Glyph_Quad { {x = 10, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE}, }, }, ) testing.expect(t, gpu.frame_planned_coverage_path(&glyph_frame) == .Planned) } @(test) test_frame_planned_coverage_keeps_unsupported_generated_forms_on_migration :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) offscreen_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&offscreen_frame) target := gpu.begin_target( &offscreen_frame, gpu.offscreen_target(ctx, {name = "offscreen", width = 64, height = 64}), ) testing.expect(t, target != gpu.INVALID_TARGET) _ = gpu.draw_rect(&offscreen_frame, {x = 1, y = 2, width = 3, height = 4, color = gpu.WHITE}) testing.expect(t, gpu.frame_planned_coverage_path(&offscreen_frame) == .Migration) } @(test) test_frame_planned_coverage_marks_indirect_draw_planned :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) geometry := ir.add_buffer(&frame.ir, "geometry", 256, {.Vertex, .Index}, {.Device_Local}, .Persistent) args := ir.add_buffer( &frame.ir, "args", u64(size_of(bk.Indirect_Draw_Indexed_Args)), {.Indirect_Argument}, {.Device_Local}, .Persistent, ) pass := ir.add_pass(&frame.ir, {kind = .Render, name = "main"}) vertex_shader := ir.add_shader(&frame.ir, {stage = .Vertex, name = "vs"}) fragment_shader := ir.add_shader(&frame.ir, {stage = .Fragment, name = "fs"}) pipeline_state := ir.default_graphics_pipeline_state() pipeline_state.vertex_shader = vertex_shader pipeline_state.fragment_shader = fragment_shader pipeline := ir.add_graphics_pipeline(&frame.ir, "pipeline", pipeline_state) _ = ir.add_draw_packet( &frame.ir, pass, pipeline, .Indirect, { geometry = {kind = .Indexed_Mesh, resource = geometry}, instances = { indirect = args, count = 1, stride = u32(size_of(bk.Indirect_Draw_Indexed_Args)), }, order = .Strict, }, ) testing.expect(t, gpu.frame_planned_coverage_path(&frame) == .Planned) } @(test) test_frame_runtime_mode_accepts_generated_2d_without_explicit_target :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.draw_rect(&frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Generated_2D) } @(test) test_frame_runtime_mode_accepts_offscreen_generated_2d :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) offscreen := gpu.begin_target( &frame, { kind = .Offscreen, name = "offscreen", width = 128, height = 128, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, clear_color = gpu.BLACK, clear_depth = 1, }, ) _ = gpu.push_layer(&frame, {name = "offscreen-layer"}) rect := gpu.draw_rect(&frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}) testing.expect(t, offscreen != gpu.INVALID_TARGET) testing.expect(t, rect != gpu.INVALID_COMMAND) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Generated_2D) testing.expect(t, gpu.submit(&frame)) } @(test) test_frame_runtime_mode_rejects_offscreen_meshes :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) offscreen := gpu.begin_target( &frame, gpu.offscreen_target( ctx, { name = "mesh-offscreen", width = 128, height = 128, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, clear_color = gpu.BLACK, clear_depth = 1, }, ), ) _ = gpu.push_layer(&frame, {name = "mesh-offscreen-layer"}) gpu.set_camera_3d(&frame, test_camera_3d()) mesh_geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 3, index_count = 3}) mesh := gpu.draw_instances(&frame, {geometry = mesh_geometry, instances = {count = 1}}) testing.expect(t, offscreen != gpu.INVALID_TARGET) testing.expect(t, mesh != gpu.INVALID_COMMAND) _, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, !ok) } @(test) test_frame_api_offscreen_target_materializes_resources_and_pass :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) target := gpu.begin_target( &frame, gpu.offscreen_target( ctx, { name = "offscreen", width = 128, height = 64, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, clear_color = gpu.BLACK, clear_depth = 1, }, ), ) testing.expect(t, target != gpu.INVALID_TARGET) testing.expect(t, gpu.submit(&frame)) fir := gpu.frame_ir(&frame) testing.expect_value(t, len(fir.resources), 2) testing.expect_value(t, len(fir.passes), 1) testing.expect(t, fir.targets[0].kind == .Offscreen) testing.expect(t, fir.targets[0].pass != gpu.INVALID_PASS) testing.expect(t, fir.resources[0].kind == .Texture) testing.expect(t, .Color_Attachment in fir.resources[0].texture.usage) testing.expect(t, .Sampled in fir.resources[0].texture.usage) testing.expect(t, fir.resources[1].kind == .Texture) testing.expect(t, .Depth_Stencil_Attachment in fir.resources[1].texture.usage) testing.expect(t, fir.passes[0].kind == .Render) testing.expect_value(t, fir.passes[0].color_target_count, u8(1)) testing.expect(t, fir.passes[0].has_depth_target) } @(test) test_frame_api_offscreen_target_rejects_invalid_dimensions :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target( &frame, gpu.offscreen_target( ctx, {name = "bad-offscreen", width = 0, height = 64, color_format = .R8G8B8A8_UNORM}, ), ) testing.expect(t, !gpu.submit(&frame)) } @(test) test_frame_api_samples_offscreen_target_as_resource_quad :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) offscreen := gpu.begin_target( &frame, gpu.offscreen_target( ctx, { name = "sampled-offscreen", width = 128, height = 64, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, clear_color = gpu.BLACK, clear_depth = 1, }, ), ) _ = gpu.push_layer(&frame, {name = "offscreen-layer"}) _ = gpu.draw_rect(&frame, {x = 8, y = 8, width = 32, height = 24, color = gpu.RED}) present := gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.push_layer(&frame, {name = "present-layer"}) sampled := gpu.draw_offscreen_texture_quad( &frame, {target = offscreen, x = 16, y = 16, width = 256, height = 128, color = gpu.WHITE}, ) testing.expect(t, offscreen != gpu.INVALID_TARGET) testing.expect(t, present != gpu.INVALID_TARGET) testing.expect(t, sampled != gpu.INVALID_COMMAND) fir := gpu.frame_ir(&frame) offscreen_desc, target_ok := ir.get_target(fir, offscreen) testing.expect(t, target_ok) testing.expect(t, offscreen_desc.color_resource != gpu.INVALID_RESOURCE) color_desc, color_ok := ir.get_resource(fir, offscreen_desc.color_resource) testing.expect(t, color_ok) testing.expect(t, .Sampled in color_desc.texture.usage) draw, draw_ok := ir.get_draw(fir, sampled) testing.expect(t, draw_ok) testing.expect(t, draw.packet.geometry.kind == .Quad_Stream) testing.expect(t, draw.packet.geometry.resource != offscreen_desc.color_resource) mode, mode_ok := gpu.frame_runtime_mode(&frame) testing.expect(t, mode_ok) testing.expect(t, mode == .Generated_2D) testing.expect(t, gpu.submit(&frame)) } @(test) test_frame_api_rejects_invalid_offscreen_sampling_targets :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) present_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&present_frame) present := gpu.begin_target(&present_frame, gpu.present_target(ctx)) present_sample := gpu.draw_offscreen_texture_quad( &present_frame, {target = present, x = 0, y = 0, color = gpu.WHITE}, ) testing.expect(t, present_sample == gpu.INVALID_COMMAND) self_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&self_frame) offscreen := gpu.begin_target( &self_frame, gpu.offscreen_target( ctx, { name = "self-sample", width = 64, height = 64, color_format = .R8G8B8A8_UNORM, depth_format = .D32_SFLOAT, clear_color = gpu.BLACK, clear_depth = 1, }, ), ) self_sample := gpu.draw_offscreen_texture_quad( &self_frame, {target = offscreen, x = 0, y = 0, color = gpu.WHITE}, ) testing.expect(t, self_sample == gpu.INVALID_COMMAND) } @(test) test_frame_runtime_plan_orders_sortable_draws :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.push_layer(&frame, {name = "world", sort_base = 0, order = .Sortable}) first := gpu.draw_rect( &frame, { x = 10, y = 10, width = 10, height = 10, color = gpu.RED, sort_key = 30, order = .Sortable, }, ) second := gpu.draw_rect( &frame, { x = 20, y = 10, width = 10, height = 10, color = gpu.GREEN, sort_key = 10, order = .Sortable, }, ) third := gpu.draw_line_frame( &frame, {x0 = 0, y0 = 0, x1 = 100, y1 = 100, color = gpu.BLUE, sort_key = 20, order = .Sortable}, ) plan := gpu.frame_runtime_plan(&frame) defer gpu.destroy_frame_runtime_plan(&plan) testing.expect_value(t, len(plan), 3) testing.expect(t, plan[0].handle == second) testing.expect(t, plan[1].handle == third) testing.expect(t, plan[2].handle == first) } @(test) test_frame_runtime_plan_keeps_mixed_order :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.push_layer(&frame, {name = "world", sort_base = 0, order = .Sortable}) gpu.set_camera_3d(&frame, test_camera_3d()) first_2d := gpu.draw_rect( &frame, { x = 10, y = 10, width = 10, height = 10, color = gpu.RED, sort_key = 10, order = .Sortable, }, ) mesh_geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 3, index_count = 3}) mesh := gpu.draw_instances( &frame, {geometry = mesh_geometry, instances = {count = 1}, sort_key = 20, order = .Sortable}, ) second_2d := gpu.draw_texture_quad( &frame, { texture = {id = 11, width = 16, height = 16}, x = 30, y = 30, color = gpu.WHITE, sort_key = 30, order = .Sortable, }, ) plan := gpu.frame_runtime_plan(&frame) defer gpu.destroy_frame_runtime_plan(&plan) testing.expect_value(t, len(plan), 3) testing.expect(t, plan[0].handle == first_2d) testing.expect(t, plan[1].handle == mesh) testing.expect(t, plan[2].handle == second_2d) } @(test) test_frame_runtime_plan_keeps_glyph_mesh_order :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.push_layer(&frame, {name = "world", sort_base = 0, order = .Sortable}) gpu.set_camera_3d(&frame, test_camera_3d()) material := gpu.add_texture_material( &frame, {id = 22, width = 128, height = 128}, {name = "glyph-atlas"}, ) glyphs := gpu.draw_text_run( &frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 8, height = 12, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, }, material = material, sort_key = 10, order = .Sortable, }, ) mesh_geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 3, index_count = 3}) mesh := gpu.draw_instances( &frame, {geometry = mesh_geometry, instances = {count = 1}, sort_key = 20, order = .Sortable}, ) plan := gpu.frame_runtime_plan(&frame) defer gpu.destroy_frame_runtime_plan(&plan) testing.expect_value(t, len(plan), 2) testing.expect(t, plan[0].handle == glyphs) testing.expect(t, plan[1].handle == mesh) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Mixed) } @(test) test_frame_runtime_mode_accepts_mixed_generated_2d_and_meshes :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) rect_mesh_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&rect_mesh_frame) _ = gpu.begin_target(&rect_mesh_frame, gpu.present_target(ctx)) gpu.set_camera_3d(&rect_mesh_frame, test_camera_3d()) _ = gpu.draw_rect( &rect_mesh_frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}, ) rect_mesh_geometry := gpu.import_mesh_geometry( &rect_mesh_frame, {id = 7, vertex_count = 3, index_count = 3}, ) _ = gpu.draw_instances( &rect_mesh_frame, {geometry = rect_mesh_geometry, instances = {count = 1}}, ) rect_mesh_mode, rect_mesh_ok := gpu.frame_runtime_mode(&rect_mesh_frame) testing.expect(t, rect_mesh_ok) testing.expect(t, rect_mesh_mode == .Mixed) circle_mesh_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&circle_mesh_frame) _ = gpu.begin_target(&circle_mesh_frame, gpu.present_target(ctx)) gpu.set_camera_3d(&circle_mesh_frame, test_camera_3d()) _ = gpu.draw_circle_frame( &circle_mesh_frame, {cx = 100, cy = 120, radius = 40, color = gpu.YELLOW}, ) circle_mesh_geometry := gpu.import_mesh_geometry( &circle_mesh_frame, {id = 7, vertex_count = 3, index_count = 3}, ) _ = gpu.draw_instances( &circle_mesh_frame, {geometry = circle_mesh_geometry, instances = {count = 1}}, ) circle_mesh_mode, circle_mesh_ok := gpu.frame_runtime_mode(&circle_mesh_frame) testing.expect(t, circle_mesh_ok) testing.expect(t, circle_mesh_mode == .Mixed) } @(test) test_frame_runtime_mode_rejects_unsupported_mixes :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) glyph_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&glyph_frame) _ = gpu.begin_target(&glyph_frame, gpu.present_target(ctx)) _ = gpu.draw_rect(&glyph_frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}) _ = gpu.draw_text_run( &glyph_frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, }, }, ) _, glyph_ok := gpu.frame_runtime_mode(&glyph_frame) testing.expect(t, !glyph_ok) circle_glyph_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&circle_glyph_frame) _ = gpu.begin_target(&circle_glyph_frame, gpu.present_target(ctx)) _ = gpu.draw_circle_frame( &circle_glyph_frame, {cx = 100, cy = 120, radius = 40, color = gpu.YELLOW}, ) _ = gpu.draw_text_run( &circle_glyph_frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, }, }, ) _, circle_glyph_ok := gpu.frame_runtime_mode(&circle_glyph_frame) testing.expect(t, !circle_glyph_ok) glyph_mesh_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&glyph_mesh_frame) _ = gpu.begin_target(&glyph_mesh_frame, gpu.present_target(ctx)) gpu.set_camera_3d(&glyph_mesh_frame, test_camera_3d()) _ = gpu.draw_text_run( &glyph_mesh_frame, { glyphs = []gpu.Glyph_Quad { { x = 10, y = 20, width = 6, height = 8, u0 = 0, v0 = 0, u1 = 1, v1 = 1, color = gpu.WHITE, }, }, }, ) glyph_mesh_geometry := gpu.import_mesh_geometry( &glyph_mesh_frame, {id = 7, vertex_count = 3, index_count = 3}, ) _ = gpu.draw_instances( &glyph_mesh_frame, {geometry = glyph_mesh_geometry, instances = {count = 1}}, ) _, glyph_mesh_ok := gpu.frame_runtime_mode(&glyph_mesh_frame) testing.expect(t, !glyph_mesh_ok) dispatch_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&dispatch_frame) _ = gpu.begin_target(&dispatch_frame, gpu.present_target(ctx)) _ = gpu.draw_rect( &dispatch_frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE}, ) _ = gpu.dispatch(&dispatch_frame, {shader = {id = 3}, groups = {1, 1, 1}}) _, dispatch_ok := gpu.frame_runtime_mode(&dispatch_frame) testing.expect(t, !dispatch_ok) } @(test) test_frame_runtime_mode_accepts_dispatch_before_draw :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) push_constants := [4]u8{1, 2, 3, 4} buffer_view := gpu.import_storage_buffer(&frame, {id = 7, size = 64}) dispatch := gpu.dispatch( &frame, { shader = {id = 3}, groups = {4, 2, 1}, storage_bindings = []gpu.Storage_Buffer_Binding{{binding = 0, buffer = buffer_view}}, push_constants = raw_data(push_constants[:]), push_constants_size = u32(len(push_constants)), barrier_after = true, sort_key = 1, }, ) rect := gpu.draw_rect( &frame, {x = 10, y = 20, width = 30, height = 40, color = gpu.WHITE, sort_key = 2}, ) fir := gpu.frame_ir(&frame) testing.expect(t, dispatch != gpu.INVALID_COMMAND) testing.expect(t, rect != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.dispatch_commands), 1) testing.expect_value(t, fir.dispatch_commands[0].shader_id, u32(3)) testing.expect_value(t, fir.dispatch_commands[0].storage_binding_count, u8(1)) testing.expect_value(t, fir.dispatch_commands[0].storage_bindings[0].binding, u32(0)) testing.expect_value( t, fir.dispatch_commands[0].storage_bindings[0].resource, buffer_view.resource, ) testing.expect_value(t, len(fir.dispatch_commands[0].push_constants), 4) testing.expect_value(t, fir.dispatch_commands[0].push_constants[0], u8(1)) testing.expect_value(t, fir.dispatch_commands[0].barrier_after, true) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Mixed) } @(test) test_frame_dispatch_rejects_invalid_storage_bindings :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) duplicate_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&duplicate_frame) _ = gpu.begin_target(&duplicate_frame, gpu.present_target(ctx)) buffer_view := gpu.import_storage_buffer(&duplicate_frame, {id = 7, size = 64}) duplicate := gpu.dispatch( &duplicate_frame, { shader = {id = 3}, groups = {1, 1, 1}, storage_bindings = []gpu.Storage_Buffer_Binding { {binding = 0, buffer = buffer_view}, {binding = 0, buffer = buffer_view}, }, }, ) testing.expect(t, duplicate == gpu.INVALID_COMMAND) non_storage_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&non_storage_frame) _ = gpu.begin_target(&non_storage_frame, gpu.present_target(ctx)) resource := ir.add_buffer( gpu.frame_ir(&non_storage_frame), "vertex", 64, {.Vertex}, {}, .Imported, ) non_storage := gpu.dispatch( &non_storage_frame, { shader = {id = 3}, groups = {1, 1, 1}, storage_bindings = []gpu.Storage_Buffer_Binding { {binding = 0, buffer = {resource = resource, size = 64}}, }, }, ) testing.expect(t, non_storage == gpu.INVALID_COMMAND) missing_side_table_frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&missing_side_table_frame) _ = gpu.begin_target(&missing_side_table_frame, gpu.present_target(ctx)) missing_resource := ir.add_buffer( gpu.frame_ir(&missing_side_table_frame), "storage", 64, {.Storage}, {}, .Imported, ) missing := gpu.dispatch( &missing_side_table_frame, { shader = {id = 3}, groups = {1, 1, 1}, storage_bindings = []gpu.Storage_Buffer_Binding { {binding = 0, buffer = {resource = missing_resource, size = 64}}, }, }, ) testing.expect(t, missing != gpu.INVALID_COMMAND) _, mode_ok := gpu.frame_runtime_mode(&missing_side_table_frame) testing.expect(t, !mode_ok) } @(test) test_frame_runtime_mode_accepts_dispatch_only :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) _ = gpu.dispatch(&frame, {shader = {id = 3}, groups = {4, 2, 1}}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Dispatches) } @(test) test_frame_runtime_mode_records_unsupported_instance_diagnostic :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 3, index_count = 3}) command := gpu.draw_instances(&frame, {geometry = geometry, instances = {count = 4}}) _, ok := gpu.frame_runtime_mode(&frame) diagnostics := gpu.frame_diagnostics(&frame) testing.expect(t, command != gpu.INVALID_COMMAND) testing.expect(t, !ok) testing.expect_value(t, len(diagnostics), 1) testing.expect(t, diagnostics[0].code == .Unsupported) testing.expect(t, diagnostics[0].command == command) } @(test) test_frame_api_dispatch_rejects_invalid_shader_without_command :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) dispatch := gpu.dispatch(&frame, {groups = {4, 2, 1}}) fir := gpu.frame_ir(&frame) testing.expect(t, dispatch == gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.dispatch_commands), 0) } @(test) test_frame_runtime_mode_accepts_imported_meshes :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) gpu.set_camera_3d(&frame, test_camera_3d()) geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 3, index_count = 3}) _ = gpu.draw_instances(&frame, {geometry = geometry, instances = {count = 1}}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Meshes) } @(test) test_frame_runtime_mode_accepts_mesh_instance_wrapper :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) gpu.set_camera_3d(&frame, test_camera_3d()) mesh := gpu.draw_mesh_instance_frame( &frame, { mesh = {id = 7, vertex_count = 24, index_count = 36}, transform = gpu.mat4_translate({1, 2, 3}), }, ) fir := gpu.frame_ir(&frame) testing.expect(t, mesh != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.resources), 1) testing.expect(t, fir.resources[0].lifetime == .Imported) testing.expect(t, fir.draw_commands[0].kind == .Indexed_Mesh) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Meshes) } @(test) test_frame_api_light_intent_is_frame_owned :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) testing.expect(t, !frame.lighting_3d_enabled) gpu.set_ambient_light_frame(&frame, {0.02, 0.03, 0.04, 1}) testing.expect(t, frame.lighting_3d_enabled) testing.expect_value(t, frame.ambient_light_3d, [4]f32{0.02, 0.03, 0.04, 1}) light := gpu.create_point_light({1, 2, 3}, {1, 0.5, 0.25, 1}, 2, 8) testing.expect(t, gpu.add_light_frame(&frame, light)) testing.expect_value(t, len(frame.lights_3d), 1) testing.expect_value(t, frame.lights_3d[0].position, [3]f32{1, 2, 3}) testing.expect_value(t, frame.lights_3d[0].radius, f32(8)) } @(test) test_frame_api_light_intent_rejects_overflow :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) light := gpu.create_point_light({0, 1, 0}) for i in 0 ..< renderer.MAX_LIGHTS { testing.expect(t, gpu.add_light_frame(&frame, light)) } testing.expect(t, !gpu.add_light_frame(&frame, light)) testing.expect_value(t, len(frame.lights_3d), renderer.MAX_LIGHTS) } @(test) test_frame_api_add_mesh_material_maps_3d_material_fields :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) material := gpu.add_mesh_material( &frame, { diffuse = {id = 11, width = 64, height = 64}, normal_map = {id = 13, width = 64, height = 64}, color = {1, 0.5, 0.25, 1}, double_sided = true, metallic = 0.2, roughness = 0.7, reflectance = 0.4, emissive = {0.1, 0.2, 0.3, 1}, }, ) fir := gpu.frame_ir(&frame) testing.expect(t, material != gpu.INVALID_MATERIAL) testing.expect_value(t, len(fir.materials), 1) testing.expect_value(t, fir.materials[0].texture_id, u32(11)) testing.expect_value(t, fir.materials[0].normal_map_texture_id, u32(13)) testing.expect_value(t, fir.materials[0].base_color, [4]f32{1, 0.5, 0.25, 1}) testing.expect(t, fir.materials[0].double_sided) testing.expect_value(t, fir.materials[0].metallic, f32(0.2)) testing.expect_value(t, fir.materials[0].roughness, f32(0.7)) testing.expect_value(t, fir.materials[0].reflectance, f32(0.4)) testing.expect_value(t, fir.materials[0].emissive, [4]f32{0.1, 0.2, 0.3, 1}) } @(test) test_frame_api_draw_model_frame_emits_materialized_mesh_packets :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) gpu.set_camera_3d(&frame, test_camera_3d()) model := gpu.Model { meshes = []gpu.Mesh { {id = 7, vertex_count = 24, index_count = 36}, {id = 8, vertex_count = 12, index_count = 18}, }, materials = []gpu.Material { { diffuse = {id = 21, width = 32, height = 32}, color = {1, 1, 1, 1}, double_sided = true, }, { diffuse = {id = 22, width = 16, height = 16}, color = {0.5, 1, 0.5, 1}, roughness = 0.8, }, }, transform = gpu.matrix_identity(), } count := gpu.draw_model_frame( &frame, {model = model, position = {1, 2, 3}, scale = 2, tint = {0.5, 1, 1, 1}, sort_key = 10}, ) fir := gpu.frame_ir(&frame) testing.expect_value(t, count, u32(2)) testing.expect_value(t, len(fir.resources), 2) testing.expect_value(t, len(fir.materials), 2) testing.expect_value(t, len(fir.draw_commands), 2) testing.expect(t, fir.draw_commands[0].kind == .Indexed_Mesh) testing.expect(t, fir.draw_commands[0].packet.material == gpu.Material_Handle(1)) testing.expect(t, fir.draw_commands[1].packet.material == gpu.Material_Handle(2)) testing.expect_value(t, fir.materials[0].texture_id, u32(21)) testing.expect(t, fir.materials[0].double_sided) testing.expect_value(t, fir.materials[0].base_color, [4]f32{0.5, 1, 1, 1}) testing.expect_value(t, fir.materials[1].texture_id, u32(22)) testing.expect_value(t, fir.materials[1].base_color, [4]f32{0.25, 1, 0.5, 1}) mode, ok := gpu.frame_runtime_mode(&frame) testing.expect(t, ok) testing.expect(t, mode == .Meshes) } @(test) test_frame_3d_builtin_producers_reject_headless_without_resources :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 640, height = 480) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) testing.expect( t, gpu.draw_cube_frame(&frame, {center = {0, 0, 0}, size = {1, 1, 1}}) == gpu.INVALID_COMMAND, ) testing.expect( t, gpu.draw_sphere_frame(&frame, {center = {0, 0, 0}, radius = 1}) == gpu.INVALID_COMMAND, ) testing.expect( t, gpu.draw_plane_frame(&frame, {center = {0, 0, 0}, size = {1, 1}}) == gpu.INVALID_COMMAND, ) testing.expect( t, gpu.draw_grid_frame(&frame, {slices = 10, spacing = 1}) == gpu.INVALID_COMMAND, ) fir := gpu.frame_ir(&frame) testing.expect_value(t, len(fir.resources), 0) testing.expect_value(t, len(fir.draw_commands), 0) } @(test) test_headless_context_destroy_is_safe :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 64, height = 64) testing.expect(t, !gpu.context_is_runtime_backed(ctx)) gpu.destroy_context(&ctx) testing.expect(t, !gpu.context_is_runtime_backed(ctx)) testing.expect_value(t, ctx.default_width, u32(0)) } @(test) test_frame_api_mesh_instances_and_dispatch :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 320, height = 240) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) _ = gpu.begin_target(&frame, gpu.present_target(ctx)) layer := gpu.push_layer(&frame, {name = "world", order = .Depth}) material := gpu.add_material(&frame, {name = "mat"}) mesh := gpu.draw_mesh_frame( &frame, { geometry = {kind = .Mesh, vertex_count = 36}, instances = {count = 1}, material = material, order = .Depth, }, ) instances := gpu.draw_instances( &frame, { geometry = {kind = .Indexed_Mesh, index_count = 6}, instances = {count = 4}, material = material, order = .Sortable, }, ) dispatch := gpu.dispatch(&frame, {shader = {id = 3}, groups = {8, 1, 1}, sort_key = 9}) fir := gpu.frame_ir(&frame) testing.expect(t, mesh != gpu.INVALID_COMMAND) testing.expect(t, instances != gpu.INVALID_COMMAND) testing.expect(t, dispatch != gpu.INVALID_COMMAND) testing.expect_value(t, len(fir.draw_commands), 2) testing.expect_value(t, len(fir.dispatch_commands), 1) testing.expect(t, fir.draw_commands[0].packet.layer == layer) testing.expect(t, fir.draw_commands[0].packet.material == material) testing.expect_value(t, fir.draw_commands[1].packet.instances.count, u32(4)) testing.expect_value(t, fir.dispatch_commands[0].sequence, u64(3)) testing.expect(t, gpu.submit(&frame)) } @(test) test_import_mesh_geometry_creates_imported_indexed_resource :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 320, height = 240) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 24, index_count = 36}) fir := gpu.frame_ir(&frame) testing.expect(t, geometry.kind == .Indexed_Mesh) testing.expect(t, geometry.resource != gpu.INVALID_RESOURCE) testing.expect_value(t, geometry.vertex_count, u32(24)) testing.expect_value(t, geometry.index_count, u32(36)) testing.expect_value(t, len(fir.resources), 1) testing.expect(t, fir.resources[0].kind == .Buffer) testing.expect(t, fir.resources[0].lifetime == .Imported) testing.expect(t, .Vertex in fir.resources[0].buffer.usage) testing.expect(t, .Index in fir.resources[0].buffer.usage) } @(test) test_import_mesh_geometry_rejects_empty_mesh :: proc(t: ^testing.T) { ctx := gpu.init_context(width = 320, height = 240) frame := gpu.begin_frame(ctx) defer gpu.destroy_frame(&frame) geometry := gpu.import_mesh_geometry(&frame, {id = 7, vertex_count = 0, index_count = 36}) fir := gpu.frame_ir(&frame) testing.expect(t, geometry.resource == gpu.INVALID_RESOURCE) testing.expect_value(t, len(fir.resources), 0) }