diff --git a/.gitignore b/.gitignore index d557e94..9454ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ zig-cache zig-out *.blend1 src/assets/models/emulator.glb +valgrind-log.txt diff --git a/run-valgrind.sh b/run-valgrind.sh new file mode 100755 index 0000000..40ffea9 --- /dev/null +++ b/run-valgrind.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -xe + +zig build +valgrind --leak-check=full --num-callers=30 --log-file=valgrind-log.txt \ + --suppressions=valgrind-suppresions/opengl_nvidia2.supp \ + --suppressions=valgrind-suppresions/opengl_nvidia1.supp \ + --suppressions=valgrind-suppresions/raylib_InitWindow.supp \ + ./zig-out/bin/chip8-zig diff --git a/src/emulator-model.zig b/src/emulator-model.zig index 426daca..b6a0f17 100644 --- a/src/emulator-model.zig +++ b/src/emulator-model.zig @@ -12,8 +12,9 @@ allocator: Allocator, materials: []rl.Material, models: std.ArrayList(rl.Model), -buttons: [16]rl.Model, -power_switch: rl.Model, +static_models: std.ArrayList(*rl.Model), +buttons: [16]*rl.Model, +power_switch: *rl.Model, bbox: rl.BoundingBox, position: rl.Vector3, @@ -31,7 +32,7 @@ fn getExtensionByMimeType(mime_type: []const u8) ?[:0]const u8 { } } -fn loadGltfImage(image: Gltf.Image) rl.Image { +fn loadGltfImage(image: Gltf.Image) !rl.Image { assert(image.uri == null); assert(image.data != null); assert(image.mime_type != null); @@ -42,18 +43,23 @@ fn loadGltfImage(image: Gltf.Image) rl.Image { const file_type = getExtensionByMimeType(mime_type); assert(file_type != null); - return rl.LoadImageFromMemory(file_type.?, @ptrCast(image_data.ptr), @intCast(image_data.len)); + const rl_image = rl.LoadImageFromMemory(file_type.?, @ptrCast(image_data.ptr), @intCast(image_data.len)); + if (@as(?*anyopaque, @ptrCast(rl_image.data)) == null) { + return error.Failed; + } + return rl_image; } fn loadGltfMaterial(data: Gltf.Data, material: Gltf.Material) !rl.Material { var rl_material = rl.LoadMaterialDefault(); - var albedo_map: *rl.MaterialMap = &rl_material.maps.?[@intFromEnum(rl.MaterialMapIndex.MATERIAL_MAP_ALBEDO)]; + errdefer rl.UnloadMaterial(rl_material); + var albedo_map: *rl.MaterialMap = &rl_material.maps.?[@intFromEnum(rl.MaterialMapIndex.MATERIAL_MAP_ALBEDO)]; if (material.metallic_roughness.base_color_texture) |base_color_texture| { const texture = data.textures.items[base_color_texture.index]; const image = data.images.items[texture.source.?]; - const rl_image = loadGltfImage(image); + const rl_image = try loadGltfImage(image); defer rl.UnloadImage(rl_image); albedo_map.texture = rl.LoadTextureFromImage(rl_image); } @@ -66,9 +72,12 @@ fn loadGltfMaterial(data: Gltf.Data, material: Gltf.Material) !rl.Material { return rl_material; } -fn loadGltfPrimitive(allocator: Allocator, gltf: Gltf, primitive: Gltf.Primitive) !rl.Mesh { +fn loadGltfPrimitive(gltf: Gltf, primitive: Gltf.Primitive) !rl.Mesh { + var allocator = std.heap.c_allocator; + assert(primitive.mode == .triangles); var rl_mesh = std.mem.zeroes(rl.Mesh); + errdefer rl.UnloadMesh(rl_mesh); var bin = gltf.glb_binary.?; var f32_buffer = std.ArrayList(f32).init(allocator); @@ -155,6 +164,85 @@ fn loadGltfPrimitive(allocator: Allocator, gltf: Gltf, primitive: Gltf.Primitive return rl_mesh; } +fn loadGltfMesh(materials: []rl.Material, gltf: Gltf, node: Gltf.Node) !rl.Model { + const allocator = std.heap.c_allocator; + const transform = Gltf.getGlobalTransform(&gltf.data, node); + + var model = std.mem.zeroes(rl.Model); + errdefer rl.UnloadModel(model); + + model.transform = rl.Matrix{ + .m0 = transform[0][0], + .m4 = transform[1][0], + .m8 = transform[2][0], + .m12 = transform[3][0], + + .m1 = transform[0][1], + .m5 = transform[1][1], + .m9 = transform[2][1], + .m13 = transform[3][1], + + .m2 = transform[0][2], + .m6 = transform[1][2], + .m10 = transform[2][2], + .m14 = transform[3][2], + + .m3 = transform[0][3], + .m7 = transform[1][3], + .m11 = transform[2][3], + .m15 = transform[3][3] + }; + + if (node.mesh) |mesh_idx| { + const mesh = gltf.data.meshes.items[mesh_idx]; + const primitives: []Gltf.Primitive = mesh.primitives.items; + + var meshes = try allocator.alloc(rl.Mesh, primitives.len); + model.meshCount = @intCast(primitives.len); + model.meshes = @ptrCast(meshes.ptr); + + var mesh_material = try allocator.alloc(i32, primitives.len); + model.meshMaterial = @ptrCast(mesh_material.ptr); + @memset(mesh_material, 0); + + var used_material_ids = try allocator.alloc(usize, materials.len); + var used_materials_count: usize = 0; + defer allocator.free(used_material_ids); + + for (0.., primitives) |j, primitive| { + meshes[j] = try loadGltfPrimitive(gltf, primitive); + + var mtl: usize = 0; + if (primitive.material) |material| { + mtl = material + 1; + } + + var mtl_index = std.mem.indexOfScalar(usize, used_material_ids[0..used_materials_count], mtl); + if (mtl_index == null) { + mtl_index = used_materials_count; + used_material_ids[used_materials_count] = mtl; + used_materials_count += 1; + } + + mesh_material[j] = @intCast(mtl_index.?); + } + + var used_materials = try allocator.alloc(rl.Material, used_materials_count+1); + model.materials = @ptrCast(used_materials); + model.materialCount = 0; + for (0..used_materials_count) |i| { + used_materials[i] = materials[used_material_ids[i]]; + + const max_material_maps = 12; + const maps = try allocator.dupe(rl.MaterialMap, used_materials[i].maps.?[0..max_material_maps]); + used_materials[i].maps = @ptrCast(maps); + model.materialCount += 1; + } + } + + return model; +} + pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { var gltf = Gltf.init(allocator); defer gltf.deinit(); @@ -164,68 +252,41 @@ pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { const scene_nodes: std.ArrayList(Gltf.Index) = scene.nodes.?; const material_count: usize = @intCast(gltf.data.materials.items.len); - var materials = try allocator.alloc(rl.Material, material_count + 1); + var materials = try allocator.alloc(rl.Material, material_count+1); + @memset(materials, std.mem.zeroes(rl.Material)); + errdefer allocator.free(materials); + materials[0] = rl.LoadMaterialDefault(); + errdefer { + for (materials) |mtl| { + rl.UnloadMaterial(mtl); + } + } + for (0..material_count, gltf.data.materials.items) |i, material| { materials[i+1] = try loadGltfMaterial(gltf.data, material); } - const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height); - var models = try std.ArrayList(rl.Model).initCapacity(allocator, scene_nodes.items.len); errdefer models.deinit(); + errdefer { + for (models.items) |model| { + rl.UnloadModel(model); + } + } - var buttons: [16]rl.Model = undefined; - var power_switch: rl.Model = undefined; + var static_models = std.ArrayList(*rl.Model).init(allocator); + errdefer static_models.deinit(); + + var buttons: [16]*rl.Model = undefined; + var power_switch: *rl.Model = undefined; for (scene_nodes.items) |node_index| { const node = gltf.data.nodes.items[node_index]; if (node.mesh == null) continue; - const transform = Gltf.getGlobalTransform(&gltf.data, node); - - var model = std.mem.zeroes(rl.Model); - model.transform = rl.Matrix{ - .m0 = transform[0][0], - .m4 = transform[1][0], - .m8 = transform[2][0], - .m12 = transform[3][0], - - .m1 = transform[0][1], - .m5 = transform[1][1], - .m9 = transform[2][1], - .m13 = transform[3][1], - - .m2 = transform[0][2], - .m6 = transform[1][2], - .m10 = transform[2][2], - .m14 = transform[3][2], - - .m3 = transform[0][3], - .m7 = transform[1][3], - .m11 = transform[2][3], - .m15 = transform[3][3] - }; - model.materials = @ptrCast(materials.ptr); - model.materialCount = @intCast(materials.len); - - const mesh = gltf.data.meshes.items[node.mesh.?]; - const primitives: []Gltf.Primitive = mesh.primitives.items; - - var meshes = try allocator.alloc(rl.Mesh, primitives.len); - var mesh_material = try allocator.alloc(i32, primitives.len); - model.meshes = @ptrCast(meshes.ptr); - model.meshCount = @intCast(primitives.len); - model.meshMaterial = @ptrCast(mesh_material.ptr); - @memset(mesh_material, 0); - - for (0.., primitives) |j, primitive| { - meshes[j] = try loadGltfPrimitive(allocator, gltf, primitive); - - if (primitive.material) |mtl| { - mesh_material[j] = @intCast(mtl+1); - } - } + models.appendAssumeCapacity(try loadGltfMesh(materials, gltf, node)); + const model = &models.items[models.items.len-1]; if (std.mem.eql(u8, node.name, "Power switch")) { power_switch = model; @@ -234,10 +295,13 @@ pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { const button_idx = try std.fmt.parseInt(usize, node.name[space+1..], 16); buttons[button_idx] = model; } else { - models.appendAssumeCapacity(model); + try static_models.append(model); } } + const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height); + errdefer rl.UnloadRenderTexture(screen_texture); + { // Link screen render target to shader var screen_mtl_idx: ?usize = null; for (1.., gltf.data.materials.items) |idx, mtl| { @@ -251,13 +315,15 @@ pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { return Self{ .allocator = allocator, + .materials = materials, + .static_models = static_models, .models = models, .buttons = buttons, .power_switch = power_switch, - .bbox = rl.GetModelBoundingBox(models.items[0]), + .bbox = rl.GetModelBoundingBox(static_models.items[0].*), .screen_texture = screen_texture, .position = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, .rl_chip = rl_chip, @@ -265,18 +331,25 @@ pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { } pub fn setShader(self: *Self, shader: rl.Shader) void { - for (self.materials) |*mtl| { - mtl.shader = shader; + for (self.models.items) |*model| { + if (model.materials == null) continue; + + for (0..@intCast(model.materialCount)) |i| { + model.materials.?[i].shader = shader; + } } } pub fn deinit(self: *Self) void { + for (self.models.items) |model| { + rl.UnloadModel(model); + } + for (self.materials) |mtl| { + rl.UnloadMaterial(mtl); + } self.allocator.free(self.materials); - - // rl.UnloadModel(self.static_model); - // for (self.button_models) |btn_model| { - // rl.UnloadModel(btn_model); - // } + self.static_models.deinit(); + self.models.deinit(); rl.UnloadRenderTexture(self.screen_texture); } @@ -308,12 +381,12 @@ pub fn draw(self: *Self) void { if (self.rl_chip.chip.is_input_pressed(@intCast(i))) { position.z += 0.035; } - rl.DrawModel(button, position, 1.0, rl.WHITE); + rl.DrawModel(button.*, position, 1.0, rl.WHITE); } - for (self.models.items) |model| { - rl.DrawModel(model, self.position, 1.0, rl.WHITE); + for (self.static_models.items) |model| { + rl.DrawModel(model.*, self.position, 1.0, rl.WHITE); } - rl.DrawModel(self.power_switch, self.position, 1.0, rl.WHITE); + rl.DrawModel(self.power_switch.*, self.position, 1.0, rl.WHITE); } diff --git a/src/main-scene.zig b/src/main-scene.zig index 164c1f6..543eacf 100644 --- a/src/main-scene.zig +++ b/src/main-scene.zig @@ -213,6 +213,7 @@ pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self { pub fn deinit(self: *Self) void { self.emulator.deinit(); + rl.UnloadShader(self.shader); rl.UnloadSound(self.chip_sound); self.chip.deinit(); self.allocator.destroy(self.raylib_chip); diff --git a/src/main.zig b/src/main.zig index 9ec66c7..a644aff 100755 --- a/src/main.zig +++ b/src/main.zig @@ -22,7 +22,7 @@ pub fn main() anyerror!void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); defer { - // if (gpa.deinit() == .leak) @panic("Leaked memory"); + if (gpa.deinit() == .leak) @panic("Leaked memory"); } rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); @@ -73,6 +73,5 @@ pub fn main() anyerror!void { rl.EndMode3D(); } rl.EndDrawing(); - } } diff --git a/valgrind-suppresions/opengl_nvidia1.supp b/valgrind-suppresions/opengl_nvidia1.supp new file mode 100644 index 0000000..417ee8d --- /dev/null +++ b/valgrind-suppresions/opengl_nvidia1.supp @@ -0,0 +1,7 @@ +{ + nvidia_realloc_zero + Memcheck:ReallocZero + fun:realloc + ... + obj:/usr/lib/libGLX_nvidia.so.* +} diff --git a/valgrind-suppresions/opengl_nvidia2.supp b/valgrind-suppresions/opengl_nvidia2.supp new file mode 100644 index 0000000..864ca68 --- /dev/null +++ b/valgrind-suppresions/opengl_nvidia2.supp @@ -0,0 +1,7 @@ +{ + nvidia_bad_size + Memcheck:BadSize + fun:posix_memalign + ... + obj:/usr/lib/libGLX_nvidia.so.* +} diff --git a/valgrind-suppresions/raylib_InitWindow.supp b/valgrind-suppresions/raylib_InitWindow.supp new file mode 100644 index 0000000..a5a271d --- /dev/null +++ b/valgrind-suppresions/raylib_InitWindow.supp @@ -0,0 +1,8 @@ +{ + raylib_InitWindow + Memcheck:Leak + ... + fun:InitWindow + ... + fun:main +}