Backlog initial-comit SDL gpu examples cooking shaders properly
This commit is contained in:
commit
641f38f295
|
|
@ -0,0 +1,10 @@
|
||||||
|
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
lib/** linguist-vendored
|
||||||
|
engine/vkImgui/cimplot/** linguist-vendored
|
||||||
|
engine/vkImgui/cimgui/** linguist-vendored
|
||||||
|
engine/papyrus/src/stb_ttf.h linguist-vendored
|
||||||
|
|
||||||
|
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs
|
||||||
|
|
||||||
|
sdks/vulkan_sdk/1.3.268.0/aarch64.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
.zig-cache/
|
||||||
|
zig-out/
|
||||||
|
|
||||||
|
*Saved/*
|
||||||
|
|
||||||
|
*zig-cache*
|
||||||
|
*zig-out*
|
||||||
|
.vscode/
|
||||||
|
*.rdbg
|
||||||
|
__pycache__
|
||||||
|
*.idea
|
||||||
|
*.orig
|
||||||
|
*_BACKUP_*
|
||||||
|
*_BASE_*
|
||||||
|
*_LOCAL_*
|
||||||
|
*_REMOTE_*
|
||||||
|
|
||||||
|
|
||||||
|
*.peterino
|
||||||
|
.vs/
|
||||||
|
CppProperties.json
|
||||||
|
|
||||||
|
packages/
|
||||||
|
*.blend1
|
||||||
|
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
*imgui.ini
|
||||||
|
*.stackdump
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
[submodule "engine/modules/graphics/lib/vulkan-zig"]
|
||||||
|
path = engine/modules/graphics/lib/vulkan-zig
|
||||||
|
url = https://github.com/peterino2/vulkan-zig.git
|
||||||
|
[submodule "engine/modules\\graphics\\lib\\objLoader"]
|
||||||
|
path = engine/modules/graphics/lib/objLoader
|
||||||
|
url = https://github.com/peterino2/zig_obj_loader.git
|
||||||
|
[submodule "engine/modules\\graphics\\lib\\cimgui"]
|
||||||
|
path = engine/modules/graphics/lib/cimgui
|
||||||
|
url = https://github.com/peterino2/cimgui.git
|
||||||
|
[submodule "engine/modules/audio/lib/miniaudio"]
|
||||||
|
path = engine/modules/audio/lib/miniaudio
|
||||||
|
url = https://github.com/mackron/miniaudio.git
|
||||||
|
[submodule "engine/projects/cognesia/zig-halcyon"]
|
||||||
|
path = engine/projects/cognesia/zig-halcyon
|
||||||
|
url = https://github.com/peterino2/zig-halcyon.git
|
||||||
|
[submodule "engine/modules/core/lib/p2"]
|
||||||
|
path = engine/modules/core/lib/p2
|
||||||
|
url = https://github.com/peterino2/p2-algorithms.git
|
||||||
|
[submodule "engine/modules/core/lib/zig-spng"]
|
||||||
|
path = engine/modules/core/lib/zig-spng
|
||||||
|
url = https://github.com/peterino2/zig-spng
|
||||||
|
[submodule "engine/modules/graphics/lib/zig-assimp"]
|
||||||
|
path = engine/modules/graphics/lib/zig-assimp
|
||||||
|
url = https://github.com/peterino2/zig-assimp.git
|
||||||
|
[submodule "engine/modules/game/zig-halcyon"]
|
||||||
|
path = engine/modules/game/zig-halcyon
|
||||||
|
url = https://github.com/peterino2/zig-halcyon.git
|
||||||
|
[submodule "engine/modules/graphics/lib/spirv-reflect-zig"]
|
||||||
|
path = engine/modules/graphics/lib/spirv-reflect-zig
|
||||||
|
url = https://github.com/peterino2/spirv-reflect-zig.git
|
||||||
|
[submodule "engine/modules/core/lib/zig_tracy"]
|
||||||
|
path = engine/modules/core/lib/zig_tracy
|
||||||
|
url = https://github.com/peterino2/zig_tracy.git
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
Copyright 2022 Peterlimail47@gmail.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Backlog
|
||||||
|
|
||||||
|
|
||||||
|
zig version: 0.14
|
||||||
|
|
||||||
|
Backlog Labs Game Engine.
|
||||||
|
|
||||||
|
run tools/scripts/first-time-setup.py
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
//!
|
||||||
|
// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac
|
||||||
|
b: *std.Build,
|
||||||
|
nw_builder: *std.Build,
|
||||||
|
target: std.Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.Mode,
|
||||||
|
nw_mod: *std.Build.Module,
|
||||||
|
spirvReflect: SpirvReflect.SpirvGenerator2,
|
||||||
|
gltf2ozz: ozz.GltfToOzz,
|
||||||
|
options: *std.Build.Step.Options,
|
||||||
|
|
||||||
|
const engineDepList = [_][]const u8{
|
||||||
|
"assets",
|
||||||
|
"audio",
|
||||||
|
"core",
|
||||||
|
"graphics",
|
||||||
|
"papyrus",
|
||||||
|
"platform",
|
||||||
|
"physics",
|
||||||
|
"ui",
|
||||||
|
"vkImgui",
|
||||||
|
};
|
||||||
|
|
||||||
|
const BuildSystem = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
const Build = std.Build;
|
||||||
|
const LazyPath = Build.LazyPath;
|
||||||
|
|
||||||
|
const SpirvReflect = @import("SpirvReflect");
|
||||||
|
const ozz = @import("ozz");
|
||||||
|
|
||||||
|
pub const InitOptions = struct {
|
||||||
|
import_name: []const u8 = "Backlog",
|
||||||
|
target: Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(b: *std.Build, opts: InitOptions) BuildSystem {
|
||||||
|
const nwdep = b.dependency(opts.import_name, .{
|
||||||
|
.target = opts.target,
|
||||||
|
.optimize = opts.optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const self = BuildSystem{
|
||||||
|
.b = b,
|
||||||
|
.nw_builder = nwdep.builder,
|
||||||
|
.target = opts.target,
|
||||||
|
.optimize = opts.optimize,
|
||||||
|
.nw_mod = nwdep.module("Backlog"),
|
||||||
|
.spirvReflect = SpirvReflect.SpirvGenerator2.init(nwdep.builder, .{}),
|
||||||
|
.options = createGameOptions(b),
|
||||||
|
.gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}),
|
||||||
|
};
|
||||||
|
|
||||||
|
b.installArtifact(self.spirvReflect.reflect);
|
||||||
|
b.installArtifact(self.gltf2ozz.exe);
|
||||||
|
|
||||||
|
const toolsInstall = b.addInstallArtifact(self.gltf2ozz.exe, .{
|
||||||
|
.dest_dir = .{ .override = .{ .custom = "tools" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const runArtifact = b.addRunArtifact(self.gltf2ozz.exe);
|
||||||
|
if (b.args) |args| {
|
||||||
|
runArtifact.addArgs(args);
|
||||||
|
}
|
||||||
|
const run_exe = b.step("gltf2ozz", "runs the gltf animation converter.");
|
||||||
|
run_exe.dependOn(&runArtifact.step);
|
||||||
|
|
||||||
|
const install_tools = b.step("tools", "installs tools needed to run the engine");
|
||||||
|
install_tools.dependOn(&toolsInstall.step);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const AddProgramOptions = struct {
|
||||||
|
name: []const u8,
|
||||||
|
desc: []const u8,
|
||||||
|
root_source_file: LazyPath,
|
||||||
|
imports: []const Build.Module.Import = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Step.Compile {
|
||||||
|
const b = self.b;
|
||||||
|
|
||||||
|
const exe = self.nw_builder.addExecutable(.{
|
||||||
|
.name = opts.name,
|
||||||
|
.target = self.target,
|
||||||
|
.optimize = self.optimize,
|
||||||
|
.root_source_file = self.nw_builder.path("engine/main.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
b.installArtifact(exe);
|
||||||
|
const runArtifact = b.addRunArtifact(exe);
|
||||||
|
if (b.args) |args| {
|
||||||
|
runArtifact.addArgs(args);
|
||||||
|
}
|
||||||
|
const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc);
|
||||||
|
run_exe.dependOn(&runArtifact.step);
|
||||||
|
|
||||||
|
// main path = name/main.zig
|
||||||
|
const mod = b.addModule(opts.name, .{
|
||||||
|
.target = self.target,
|
||||||
|
.optimize = self.optimize,
|
||||||
|
.root_source_file = opts.root_source_file,
|
||||||
|
.imports = opts.imports,
|
||||||
|
});
|
||||||
|
|
||||||
|
exe.root_module.addImport("main", mod);
|
||||||
|
exe.root_module.addImport("Backlog", self.nw_mod);
|
||||||
|
mod.addImport("Backlog", self.nw_mod);
|
||||||
|
exe.root_module.addOptions("BacklogOptions", self.options);
|
||||||
|
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/triangle_mesh.vert"), "triangle_mesh_vert");
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/default_lit.frag"), "default_lit");
|
||||||
|
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/debug.vert"), "debug_vert");
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/debug.frag"), "debug_frag");
|
||||||
|
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/skybox/skybox.vert"), "skybox_vert");
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/graphics/shaders/skybox/skybox.frag"), "skybox_frag");
|
||||||
|
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/PapyrusRect.vert"), "papyrus_vk_vert");
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/PapyrusRect.frag"), "papyrus_vk_frag");
|
||||||
|
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/FontSDF.vert"), "FontSDF_vert");
|
||||||
|
self.spirvReflect.addShaderInstallRef(exe, self.nw_builder.path("engine/ui/shaders/FontSDF.frag"), "FontSDF_frag");
|
||||||
|
|
||||||
|
b.getInstallStep().dependOn(self.nw_builder.getInstallStep());
|
||||||
|
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createGameOptions(b: *std.Build) *std.Build.Step.Options {
|
||||||
|
const opts = b.addOptions();
|
||||||
|
|
||||||
|
// build options for core.zig
|
||||||
|
opts.addOption(
|
||||||
|
bool,
|
||||||
|
"mutex_job_queue",
|
||||||
|
b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false,
|
||||||
|
);
|
||||||
|
opts.addOption(
|
||||||
|
bool,
|
||||||
|
"zero_logging",
|
||||||
|
b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false,
|
||||||
|
);
|
||||||
|
opts.addOption(
|
||||||
|
bool,
|
||||||
|
"slow_logging",
|
||||||
|
b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false,
|
||||||
|
);
|
||||||
|
opts.addOption(
|
||||||
|
bool,
|
||||||
|
"force_mailbox",
|
||||||
|
b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false,
|
||||||
|
);
|
||||||
|
opts.addOption(
|
||||||
|
bool,
|
||||||
|
"use_renderthread",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========= standalone build instance =======
|
||||||
|
// maybe it should be an engine launcher or something..
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const spirvDep = b.dependency("SpirvReflect", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
_ = spirvDep;
|
||||||
|
|
||||||
|
const mod = b.addModule("Backlog", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("engine/backlog.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (engineDepList) |depName| {
|
||||||
|
const dep = b.dependency(
|
||||||
|
depName,
|
||||||
|
.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
mod.addImport(depName, dep.module(depName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
.{
|
||||||
|
.name = "Backlog",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.assets = .{ .path = "engine/assets" },
|
||||||
|
.audio = .{ .path = "engine/audio" },
|
||||||
|
.core = .{ .path = "engine/core" },
|
||||||
|
.graphics = .{ .path = "engine/graphics" },
|
||||||
|
.papyrus = .{ .path = "engine/papyrus" },
|
||||||
|
.physics = .{ .path = "engine/physics" },
|
||||||
|
.platform = .{ .path = "engine/platform" },
|
||||||
|
.ui = .{ .path = "engine/ui" },
|
||||||
|
.vkImgui = .{ .path = "engine/vkImgui" },
|
||||||
|
|
||||||
|
.SpirvReflect = .{ .path = "lib/spirv-reflect-zig" },
|
||||||
|
.ozz = .{ .path = "lib/ozz" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
modules/*/lib/* linguist-vendored
|
||||||
|
*.zip
|
||||||
|
sdks/vulkan_sdk/1.3.268.0/aarch64.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
*.swp
|
||||||
|
zig-cache/
|
||||||
|
zig-out/
|
||||||
|
content/*~
|
||||||
|
imgui.ini
|
||||||
|
.vs/
|
||||||
|
CppProperties.json
|
||||||
|
sampleOutput.json
|
||||||
|
Saved/
|
||||||
|
*.DS_Store
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("assets", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/assets.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const core_dep = b.dependency(
|
||||||
|
"core",
|
||||||
|
.{ .target = target, .optimize = optimize },
|
||||||
|
);
|
||||||
|
mod.addImport("core", core_dep.module("core"));
|
||||||
|
|
||||||
|
const packer_dep = b.dependency(
|
||||||
|
"packer",
|
||||||
|
.{ .target = target, .optimize = optimize },
|
||||||
|
);
|
||||||
|
|
||||||
|
mod.addImport("packer", packer_dep.module("packer"));
|
||||||
|
|
||||||
|
const test_step = b.step("test", "run unit tests for assets");
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("tests/test.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.root_module.addImport("assets", mod);
|
||||||
|
const runArtifact = b.addRunArtifact(tests);
|
||||||
|
test_step.dependOn(&runArtifact.step);
|
||||||
|
b.installArtifact(tests);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.{
|
||||||
|
.name = "assets",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.core = .{ .path = "../core" },
|
||||||
|
.packer = .{ .path = "../../lib/packer" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const assets = @import("assets.zig");
|
||||||
|
|
||||||
|
const JobContext = core.JobContext;
|
||||||
|
const JobWorker = core.JobWorker;
|
||||||
|
const JobManager = core.JobManager;
|
||||||
|
|
||||||
|
const AssetReference = assets.AssetReference;
|
||||||
|
|
||||||
|
pub const AsyncAssetJobContext = struct {
|
||||||
|
mutex: std.mutex.Mutex,
|
||||||
|
assetLoadList: []const AssetReference,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn loadAssets(assetList: []const AssetReference, allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const this: *@This() = try allocator.create(@This());
|
||||||
|
|
||||||
|
this.* = .{
|
||||||
|
.mutex = .{},
|
||||||
|
.assetLoadList = assetList,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const tracy = core.tracy;
|
||||||
|
|
||||||
|
pub const AssetImportReference = struct {
|
||||||
|
assetRef: AssetRef,
|
||||||
|
properties: AssetPropertiesBag,
|
||||||
|
};
|
||||||
|
|
||||||
|
// MakeImportRef with defaults.
|
||||||
|
pub fn MakeImportRef(assetType: []const u8, name: []const u8, path: []const u8) AssetImportReference {
|
||||||
|
return .{
|
||||||
|
.assetRef = .{
|
||||||
|
.assetType = core.Name.MakeComptime(assetType),
|
||||||
|
.name = core.Name.MakeComptime(name),
|
||||||
|
},
|
||||||
|
.properties = .{ .path = path },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced version of MakeImportRef, allows user to explicitly construct the properties bag.
|
||||||
|
pub fn MakeImportRefOptions(assetType: []const u8, name: []const u8, properties: AssetPropertiesBag) AssetImportReference {
|
||||||
|
return .{
|
||||||
|
.assetRef = .{
|
||||||
|
.assetType = core.Name.MakeComptime(assetType),
|
||||||
|
.name = core.Name.MakeComptime(name),
|
||||||
|
},
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO, this needs to be replaced with some kind of polymorphic data bag I think,
|
||||||
|
// some kind of transmute is probably in order, maybe allocate 256 bytes of data
|
||||||
|
// for it.
|
||||||
|
pub const AssetPropertiesBag = struct {
|
||||||
|
path: []const u8 = "None",
|
||||||
|
soundVolume: f32 = 1.0,
|
||||||
|
soundLooping: bool = false,
|
||||||
|
textureUseBlockySampler: bool = true,
|
||||||
|
meshType: ?[]const u8 = null,
|
||||||
|
skeletonName: ?[]const u8 = null,
|
||||||
|
textureCube: bool = false,
|
||||||
|
textureList: ?[]const []const u8 = null, // if set, path texture will be loaded first, and this list of textures will be loaded together.
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetRef = struct {
|
||||||
|
name: core.Name,
|
||||||
|
assetType: core.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
// very minimal asset loading library.
|
||||||
|
pub const AssetLoaderError = error{
|
||||||
|
UnableToLoad,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetLoaderStatus = enum {
|
||||||
|
AssetReady, // The asset was loaded and ready to be installed
|
||||||
|
LoadFailed, // The asset failed for one reason or another
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetLoaderMessage = struct {
|
||||||
|
assetRef: AssetRef,
|
||||||
|
status: AssetLoaderStatus,
|
||||||
|
message: ?[]u8,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, assetRef: AssetRef, status: AssetLoaderStatus, maybeMessage: ?[]const u8) !@This() {
|
||||||
|
var m: ?[]u8 = null;
|
||||||
|
if (maybeMessage) |message| {
|
||||||
|
m = try core.dupeString(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .message = m, .allocator = allocator, .assetRef = assetRef, .status = status };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
if (self.message) |message| {
|
||||||
|
self.allocator.free(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetLoaderInterface = struct {
|
||||||
|
typeName: []const u8,
|
||||||
|
typeSize: usize,
|
||||||
|
typeAlign: usize,
|
||||||
|
|
||||||
|
assetType: []const u8,
|
||||||
|
loadAsset: *const fn (*anyopaque, AssetRef, ?AssetPropertiesBag) AssetLoaderError!void,
|
||||||
|
destroy: *const fn (*anyopaque, std.mem.Allocator) void,
|
||||||
|
discardAll: *const fn (*anyopaque) void,
|
||||||
|
|
||||||
|
pub fn from(comptime assetType: []const u8, comptime TargetType: type) @This() {
|
||||||
|
const wrappedFuncs = struct {
|
||||||
|
pub fn loadAsset(
|
||||||
|
pointer: *anyopaque,
|
||||||
|
assetRef: AssetRef,
|
||||||
|
properties: ?AssetPropertiesBag,
|
||||||
|
) AssetLoaderError!void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
try ptr.loadAsset(assetRef, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discardAll(pointer: *anyopaque) void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.discardAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(
|
||||||
|
pointer: *anyopaque,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
) void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.destroy(allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!@hasDecl(TargetType, "loadAsset")) {
|
||||||
|
@compileLog("Tried to generate AssetLoaderInterface for type ", TargetType, "but it's missing func loadAsset");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!@hasDecl(TargetType, "destroy")) {
|
||||||
|
@compileLog("Tried to generate AssetLoaderInterface for type ", TargetType, "but it's missing func destroy.");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = @This(){
|
||||||
|
.typeName = @typeName(TargetType),
|
||||||
|
.typeSize = @sizeOf(TargetType),
|
||||||
|
.typeAlign = @alignOf(TargetType),
|
||||||
|
.loadAsset = wrappedFuncs.loadAsset,
|
||||||
|
.discardAll = wrappedFuncs.discardAll,
|
||||||
|
.destroy = wrappedFuncs.destroy,
|
||||||
|
.assetType = assetType,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetLoaderRef = struct {
|
||||||
|
target: *anyopaque,
|
||||||
|
vtable: *const AssetLoaderInterface,
|
||||||
|
size: usize,
|
||||||
|
|
||||||
|
pub fn loadAsset(self: *@This(), asset: AssetRef, propertiesBag: ?AssetPropertiesBag) !void {
|
||||||
|
try self.vtable.loadAsset(self.target, asset, propertiesBag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discardAll(self: @This()) void {
|
||||||
|
self.vtable.discardAll(self.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.vtable.destroy(self.target, allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetReferenceSys = struct {
|
||||||
|
loaders: std.AutoHashMapUnmanaged(u32, AssetLoaderRef),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
outstandingAssetJobs: std.atomic.Value(i32),
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
|
return @This(){
|
||||||
|
.loaders = .{},
|
||||||
|
.allocator = allocator,
|
||||||
|
.outstandingAssetJobs = std.atomic.Value(i32).init(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerLoader(self: *@This(), loader: anytype) !void {
|
||||||
|
const vtable = &@field(@TypeOf(loader.*), "LoaderInterfaceVTable");
|
||||||
|
var assetType = core.MakeName(vtable.assetType);
|
||||||
|
try self.loaders.put(self.allocator, assetType.handle(), .{
|
||||||
|
.vtable = vtable,
|
||||||
|
.target = loader,
|
||||||
|
.size = @sizeOf(@TypeOf(loader)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadRef(self: *@This(), asset: AssetRef, propertiesBag: ?AssetPropertiesBag) !void {
|
||||||
|
if (core.getEngine().isShuttingDown())
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ = self.outstandingAssetJobs.fetchAdd(1, .acquire);
|
||||||
|
|
||||||
|
var z = tracy.ZoneN(@src(), "AssetReferenceSys LoadRef");
|
||||||
|
var assetName = asset.name;
|
||||||
|
var assetType = asset.assetType;
|
||||||
|
|
||||||
|
if (propertiesBag) |props| {
|
||||||
|
core.engine_log("loading asset {s} ({s}) [{s}]", .{ assetName.utf8(), assetType.utf8(), props.path });
|
||||||
|
} else {
|
||||||
|
core.engine_log("loading asset {s} ({s})", .{ assetName.utf8(), assetType.utf8() });
|
||||||
|
}
|
||||||
|
var n = asset.assetType;
|
||||||
|
try self.loaders.getPtr(n.handle()).?.loadAsset(asset, propertiesBag);
|
||||||
|
z.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
var iter = self.loaders.valueIterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
i.destroy(self.allocator);
|
||||||
|
}
|
||||||
|
self.loaders.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const memory = core.MemoryTracker;
|
||||||
|
|
||||||
|
pub const asset_references = @import("asset_references.zig");
|
||||||
|
pub const asset_jobs = @import("asset_jobs.zig");
|
||||||
|
pub const cook = @import("cook.zig");
|
||||||
|
|
||||||
|
pub const AssetRef = asset_references.AssetRef;
|
||||||
|
pub const AssetReference = asset_references.AssetReference;
|
||||||
|
pub const AssetLoaderError = asset_references.AssetLoaderError;
|
||||||
|
pub const AssetLoaderInterface = asset_references.AssetLoaderInterface;
|
||||||
|
pub const AssetImportReference = asset_references.AssetImportReference;
|
||||||
|
pub const AssetPropertiesBag = asset_references.AssetPropertiesBag;
|
||||||
|
pub const AssetReferenceSys = asset_references.AssetReferenceSys;
|
||||||
|
|
||||||
|
pub const MakeImportRef = asset_references.MakeImportRef;
|
||||||
|
pub const MakeImportRefOptions = asset_references.MakeImportRefOptions;
|
||||||
|
|
||||||
|
pub const AsyncAssetJobContext = asset_jobs.AsyncAssetJobContext;
|
||||||
|
|
||||||
|
pub var gAssetSys: *AssetReferenceSys = undefined;
|
||||||
|
|
||||||
|
pub const Module = core.ModuleDescription{
|
||||||
|
.name = "assets",
|
||||||
|
.enabledByDefault = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var cooking: bool = false;
|
||||||
|
|
||||||
|
pub fn start_module(comptime spec: anytype, args: anytype, allocator: std.mem.Allocator) !void {
|
||||||
|
_ = args;
|
||||||
|
|
||||||
|
if (@hasField(@TypeOf(spec), "cooking")) {
|
||||||
|
core.engine_logs("Cooking Enabled");
|
||||||
|
cooking = true;
|
||||||
|
try cook.startup(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
gAssetSys = allocator.create(AssetReferenceSys) catch @panic("unable to initialize asset reference");
|
||||||
|
gAssetSys.* = AssetReferenceSys.init(allocator);
|
||||||
|
|
||||||
|
memory.MTPrintStatsDelta();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_module(allocator: std.mem.Allocator) void {
|
||||||
|
if (cooking) {
|
||||||
|
cook.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
gAssetSys.deinit();
|
||||||
|
allocator.destroy(gAssetSys);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadList(assetList: anytype) !void {
|
||||||
|
for (assetList) |assetImport| {
|
||||||
|
try load(assetImport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(assetImport: AssetImportReference) !void {
|
||||||
|
var z1 = core.tracy.ZoneN(@src(), "Loading asset");
|
||||||
|
var assetRefName = assetImport.assetRef.name;
|
||||||
|
core.tracy.Message(assetRefName.utf8());
|
||||||
|
core.tracy.Message(assetImport.properties.path);
|
||||||
|
try gAssetSys.loadRef(assetImport.assetRef, assetImport.properties);
|
||||||
|
z1.End();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
pub const CookResultEnum = enum {
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FileBytes = struct {
|
||||||
|
path: []const u8,
|
||||||
|
bytes: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtraFiles = std.ArrayList(FileBytes);
|
||||||
|
|
||||||
|
pub const CookResult = struct {
|
||||||
|
bytes: std.ArrayList(u8),
|
||||||
|
result: CookResultEnum = .Success,
|
||||||
|
extraFiles: ?ExtraFiles = null,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.bytes.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CookParams = struct {
|
||||||
|
noSave: bool = false,
|
||||||
|
fileName: []const u8,
|
||||||
|
cookFileName: []const u8,
|
||||||
|
};
|
||||||
|
pub const CookInfo = struct {
|
||||||
|
assetType: []const u8 = "none",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CookRegistry = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cooks: std.StringHashMapUnmanaged(CookFunction) = .{},
|
||||||
|
generates: std.StringHashMapUnmanaged(CookGenerateFunction) = .{},
|
||||||
|
|
||||||
|
typesByExt: std.StringHashMapUnmanaged([]const u8) = .{},
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
gCookRegistry = self;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install(self: *@This(), typeName: []const u8, generate: CookGenerateFunction, cook: CookFunction, extensions: []const []const u8) !void {
|
||||||
|
try self.generates.put(self.allocator, typeName, generate);
|
||||||
|
try self.cooks.put(self.allocator, typeName, cook);
|
||||||
|
|
||||||
|
for (extensions) |ext| {
|
||||||
|
try self.typesByExt.put(self.allocator, ext, typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.cooks.deinit(self.allocator);
|
||||||
|
self.typesByExt.deinit(self.allocator);
|
||||||
|
self.generates.deinit(self.allocator);
|
||||||
|
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var gCookRegistry: *CookRegistry = undefined;
|
||||||
|
var gCooking: bool = false;
|
||||||
|
|
||||||
|
pub fn getRegistry() *CookRegistry {
|
||||||
|
return gCookRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn startup(allocator: std.mem.Allocator) !void {
|
||||||
|
gCooking = true;
|
||||||
|
_ = try CookRegistry.create(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown() void {
|
||||||
|
if (gCooking) {
|
||||||
|
gCookRegistry.destroy();
|
||||||
|
gCooking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const GenerateError = error{UnableToGenerate};
|
||||||
|
pub const LoadError = error{BadFile};
|
||||||
|
|
||||||
|
pub const CookGenerateFunction = *const fn (allocator: std.mem.Allocator, filePath: []const u8, out: *std.ArrayList(u8)) GenerateError!void;
|
||||||
|
pub const CookFunction = *const fn (allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, params: CookParams) CookResult;
|
||||||
|
|
||||||
|
pub fn loadFileAlloc(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) ![]align(8) u8 {
|
||||||
|
var file = try dir.openFile(path, .{});
|
||||||
|
defer file.close();
|
||||||
|
const filesize = (try file.stat()).size + 1; // add null byte
|
||||||
|
const buffer: []align(8) u8 = try allocator.alignedAlloc(u8, 8, filesize);
|
||||||
|
errdefer allocator.free(buffer);
|
||||||
|
try file.reader().readNoEof(buffer[0 .. buffer.len - 1]);
|
||||||
|
buffer[buffer.len - 1] = 0;
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getExtension(path: []const u8) ?[]const u8 {
|
||||||
|
var extension: []const u8 = path;
|
||||||
|
var i: i32 = @intCast(path.len - 1);
|
||||||
|
while (i > 0) : (i -= 1) {
|
||||||
|
if (path[@intCast(i)] == '.') {
|
||||||
|
extension = path[@intCast(i)..];
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generateCookFile(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) !void {
|
||||||
|
if (getExtension(path)) |extension| {
|
||||||
|
if (gCookRegistry.typesByExt.get(extension)) |assetType| {
|
||||||
|
var cookFilePath = std.ArrayList(u8).init(allocator);
|
||||||
|
defer cookFilePath.deinit();
|
||||||
|
const generateFunction = gCookRegistry.generates.get(assetType).?;
|
||||||
|
|
||||||
|
var outPath: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
defer outPath.deinit(allocator);
|
||||||
|
|
||||||
|
try outPath.appendSlice(allocator, path);
|
||||||
|
try outPath.appendSlice(allocator, ".cook");
|
||||||
|
|
||||||
|
try generateFunction(allocator, path, &cookFilePath);
|
||||||
|
|
||||||
|
const file = try dir.createFile(outPath.items, .{});
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
try file.writeAll(cookFilePath.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookFile(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) !void {
|
||||||
|
std.debug.print("{s}\n", .{path});
|
||||||
|
const extension = getExtension(path).?;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, extension, ".cook")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookFilePath = std.ArrayList(u8).init(allocator);
|
||||||
|
defer cookFilePath.deinit();
|
||||||
|
|
||||||
|
try cookFilePath.appendSlice(path);
|
||||||
|
try cookFilePath.appendSlice(".cook");
|
||||||
|
|
||||||
|
if (gCookRegistry.typesByExt.get(extension)) |assetType| {
|
||||||
|
const cookFunction = gCookRegistry.cooks.get(assetType).?;
|
||||||
|
|
||||||
|
dir.access(cookFilePath.items, .{}) catch return;
|
||||||
|
|
||||||
|
var results = cookFunction(allocator, dir, path, .{
|
||||||
|
.cookFileName = cookFilePath.items,
|
||||||
|
.fileName = path,
|
||||||
|
});
|
||||||
|
defer results.deinit();
|
||||||
|
|
||||||
|
var outPath = std.ArrayList(u8).init(allocator);
|
||||||
|
defer outPath.deinit();
|
||||||
|
|
||||||
|
try outPath.appendSlice("_cooked/");
|
||||||
|
try outPath.appendSlice(path);
|
||||||
|
try outPath.appendSlice(".");
|
||||||
|
try outPath.appendSlice(assetType);
|
||||||
|
|
||||||
|
{
|
||||||
|
var basePath: []const u8 = outPath.items;
|
||||||
|
var index: usize = basePath.len - 1;
|
||||||
|
while (basePath[index] != '/' and basePath[index] != '\\') : (index -= 1) {}
|
||||||
|
basePath = basePath[0..index];
|
||||||
|
try dir.makePath(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.result == .Success) {
|
||||||
|
const file = try dir.createFile(outPath.items, .{});
|
||||||
|
defer file.close();
|
||||||
|
try file.writeAll(results.bytes.items);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generateAllCookFiles(allocator: std.mem.Allocator, dir: std.fs.Dir) !void {
|
||||||
|
var iter = dir.iterate();
|
||||||
|
while (try iter.next()) |next| {
|
||||||
|
switch (next.kind) {
|
||||||
|
.file => {
|
||||||
|
try generateCookFile(allocator, dir, next.name);
|
||||||
|
},
|
||||||
|
.directory => {
|
||||||
|
if (!std.mem.eql(u8, "_cooked", next.name)) {
|
||||||
|
var subDir = try dir.openDir(next.name, .{ .iterate = true });
|
||||||
|
defer subDir.close();
|
||||||
|
|
||||||
|
try generateAllCookFiles(allocator, subDir);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookAllFiles(allocator: std.mem.Allocator, dir: std.fs.Dir) !void {
|
||||||
|
var walker = try dir.walk(allocator);
|
||||||
|
defer walker.deinit();
|
||||||
|
|
||||||
|
while (try walker.next()) |next| {
|
||||||
|
switch (next.kind) {
|
||||||
|
.file => {
|
||||||
|
if (std.mem.startsWith(u8, next.path, "_cooked")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, next.path, ".gitignore")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try cookFile(allocator, dir, next.path);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "testing cooking" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
|
||||||
|
const Test = struct {
|
||||||
|
const TestConfig = struct {
|
||||||
|
info: CookInfo = .{ .assetType = "Texture" }, // there must always be a CookInfo field
|
||||||
|
sourceType: []const u8 = "png",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generateFunction(allocator: std.mem.Allocator, path: []const u8, out: *std.ArrayList(u8)) GenerateError!void {
|
||||||
|
_ = allocator;
|
||||||
|
_ = path;
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
|
||||||
|
std.json.stringify(TestConfig{}, .{ .whitespace = .indent_4 }, out.writer()) catch return GenerateError.UnableToGenerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookFunction(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, params: CookParams) CookResult {
|
||||||
|
_ = params;
|
||||||
|
const rawFileBytes = loadFileAlloc(allocator, dir, path) catch unreachable;
|
||||||
|
defer allocator.free(rawFileBytes);
|
||||||
|
const rv = std.ArrayList(u8).init(allocator);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.bytes = rv,
|
||||||
|
.result = .Success,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dir = try std.fs.cwd().openDir("test", .{ .iterate = true });
|
||||||
|
|
||||||
|
const registry = try CookRegistry.create(alloc);
|
||||||
|
defer registry.destroy();
|
||||||
|
try registry.generates.put(registry.allocator, "Texture", Test.generateFunction);
|
||||||
|
try registry.cooks.put(registry.allocator, "Texture", Test.cookFunction);
|
||||||
|
try registry.typesByExt.put(registry.allocator, ".png", "Texture");
|
||||||
|
|
||||||
|
// scan and generate all texture files
|
||||||
|
try generateAllCookFiles(alloc, dir);
|
||||||
|
try cookAllFiles(alloc, dir);
|
||||||
|
// try generateCookFile(alloc, dir, "icon.png");
|
||||||
|
// try cookFile(alloc, dir, "icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
_cooked/
|
||||||
|
*.cook
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
|
|
@ -0,0 +1,36 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const depList = [_][]const u8{
|
||||||
|
"miniaudio",
|
||||||
|
"core",
|
||||||
|
"assets",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("audio", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/audio.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (depList) |depName| {
|
||||||
|
const dep = b.dependency(depName, .{ .target = target, .optimize = optimize });
|
||||||
|
|
||||||
|
mod.addImport(depName, dep.module(depName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_step = b.step("test", "run unit tests for audio");
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("tests/tests.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.root_module.addImport("audio", mod);
|
||||||
|
const runArtifact = b.addRunArtifact(tests);
|
||||||
|
test_step.dependOn(&runArtifact.step);
|
||||||
|
b.installArtifact(tests);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
.{
|
||||||
|
.name = "audio",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
//
|
||||||
|
.miniaudio = .{ .path = "../../lib/miniaudio" },
|
||||||
|
|
||||||
|
// 3rd party libs
|
||||||
|
.assets = .{ .path = "../assets" },
|
||||||
|
.core = .{ .path = "../core" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
const core = @import("core");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const std = @import("std");
|
||||||
|
const memory = core.MemoryTracker;
|
||||||
|
|
||||||
|
const soundEngine = @import("sound_engine.zig");
|
||||||
|
pub const NeonSoundEngine = soundEngine.NeonSoundEngine;
|
||||||
|
pub const sound_err = soundEngine.sound_err;
|
||||||
|
pub const sound_errs = soundEngine.sound_errs;
|
||||||
|
pub const sound_log = soundEngine.sound_log;
|
||||||
|
pub const sound_logs = soundEngine.sound_logs;
|
||||||
|
|
||||||
|
pub var gSoundEngine: *NeonSoundEngine = undefined;
|
||||||
|
pub var gSoundLoader: *soundEngine.SoundLoader = undefined;
|
||||||
|
|
||||||
|
pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void {
|
||||||
|
_ = args;
|
||||||
|
_ = programSpec;
|
||||||
|
if (core.isUtility()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gSoundEngine = core.gEngine.createObject(NeonSoundEngine, .{ .can_tick = true }) catch unreachable;
|
||||||
|
gSoundLoader = allocator.create(soundEngine.SoundLoader) catch unreachable;
|
||||||
|
gSoundLoader.* = soundEngine.SoundLoader.init(gSoundEngine);
|
||||||
|
|
||||||
|
assets.gAssetSys.registerLoader(gSoundLoader) catch unreachable;
|
||||||
|
gSoundEngine.loadSound(core.MakeName("s_test"), "content/sounds/engineTick.wav", .{}) catch unreachable;
|
||||||
|
|
||||||
|
core.engine_logs("sound start_module");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_module(allocator: std.mem.Allocator) void {
|
||||||
|
_ = allocator;
|
||||||
|
gSoundEngine.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Module = core.ModuleDescription{
|
||||||
|
.name = "audio",
|
||||||
|
.enabledByDefault = false,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
// Implemented via miniaudio
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const assets = @import("assets");
|
||||||
|
|
||||||
|
const Name = core.Name;
|
||||||
|
const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
|
||||||
|
const tracy = core.tracy;
|
||||||
|
|
||||||
|
const ma = @import("miniaudio");
|
||||||
|
|
||||||
|
pub fn sound_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
core.printInner("[SOUND ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sound_logs(comptime fmt: []const u8) void {
|
||||||
|
core.printInner("[SOUND ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sound_err(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
core.printInner("[SOUND ]: ERROR!! " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sound_errs(comptime fmt: []const u8) void {
|
||||||
|
core.printInner("[SOUND ]: ERROR!!" ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SoundLoader = struct {
|
||||||
|
pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from(core.MakeName("Sound"), @This());
|
||||||
|
|
||||||
|
engine: *NeonSoundEngine,
|
||||||
|
|
||||||
|
pub fn init(engine: *NeonSoundEngine) @This() {
|
||||||
|
return @This(){
|
||||||
|
.engine = engine,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discardAll(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfortunately this one is blocking
|
||||||
|
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, properties: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
|
||||||
|
core.engine_log("loading sound asset {s}", .{properties.?.path});
|
||||||
|
self.engine.loadSound(assetRef.name, properties.?.path, .{
|
||||||
|
.volume = properties.?.soundVolume,
|
||||||
|
}) catch {
|
||||||
|
core.engine_log("unable to load sound {s}", .{assetRef.name.utf8()});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn ma_res(value: anytype) !void {
|
||||||
|
if (value != ma.MA_SUCCESS) {
|
||||||
|
core.engine_err("miniaudio error value: {d}", .{value});
|
||||||
|
return error.MA_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// On init, SoundEngine will spawn a
|
||||||
|
pub const NeonSoundEngine = struct {
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
engine: *ma.ma_engine,
|
||||||
|
sounds: AutoHashMapUnmanaged(u32, *ma.ma_sound),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
volume: f32 = 1.0,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = @This(){
|
||||||
|
.engine = allocator.create(ma.ma_engine) catch unreachable,
|
||||||
|
.sounds = .{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = ma.ma_engine_init(null, self.engine);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(self: *@This()) void {
|
||||||
|
ma.ma_engine_uninit(self.engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadSound(
|
||||||
|
self: *@This(),
|
||||||
|
soundName: core.Name,
|
||||||
|
fileName: []const u8,
|
||||||
|
soundParams: struct {
|
||||||
|
looping: bool = false,
|
||||||
|
volume: f32 = 1.0,
|
||||||
|
},
|
||||||
|
) !void {
|
||||||
|
const sound = try self.allocator.create(ma.ma_sound);
|
||||||
|
errdefer self.allocator.destroy(sound);
|
||||||
|
|
||||||
|
const res = ma.ma_sound_init_from_file(self.engine, fileName.ptr, 0, null, null, sound);
|
||||||
|
if (res != ma.MA_SUCCESS) {
|
||||||
|
sound_err("tried loading sound: {s} failed", .{soundName.utf8()});
|
||||||
|
return error.MiniAudioError;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.sounds.put(self.allocator, soundName.handle(), sound);
|
||||||
|
ma.ma_sound_set_volume(sound, soundParams.volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playSound(self: *@This(), soundName: core.Name) !void {
|
||||||
|
const maybeSound = self.sounds.get(soundName.handle());
|
||||||
|
|
||||||
|
if (maybeSound == null)
|
||||||
|
return error.NoSoundError;
|
||||||
|
|
||||||
|
const sound = maybeSound.?;
|
||||||
|
|
||||||
|
if (ma.ma_sound_start(sound) != ma.MA_SUCCESS)
|
||||||
|
sound_err("unable to start sound: {s}", .{soundName.utf8()});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setVolume(self: *@This(), volume: f32) void {
|
||||||
|
self.volume = volume;
|
||||||
|
_ = ma.ma_engine_set_volume(self.engine, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stopSound(self: *@This(), soundName: core.Name) void {
|
||||||
|
const sound = self.sounds.get(soundName.handle()).?;
|
||||||
|
|
||||||
|
if (ma.ma_sound_stop(sound) != ma.MA_SUCCESS) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
var iter = self.sounds.iterator();
|
||||||
|
while (iter.next()) |sound| {
|
||||||
|
self.allocator.destroy(sound.value_ptr.*);
|
||||||
|
}
|
||||||
|
self.sounds.deinit(self.allocator);
|
||||||
|
// ma.ma_engine_uninit(self.engine);
|
||||||
|
self.allocator.destroy(self.engine);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), deltaTime: f64) void {
|
||||||
|
var z = tracy.ZoneNC(@src(), "audio engine tick", 0xABBADD);
|
||||||
|
defer z.End();
|
||||||
|
|
||||||
|
_ = self;
|
||||||
|
_ = deltaTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const dependencyList = [_][]const u8{
|
||||||
|
"spng",
|
||||||
|
"nfd",
|
||||||
|
"p2",
|
||||||
|
"tracy",
|
||||||
|
"zmath",
|
||||||
|
"packer",
|
||||||
|
"zgltf",
|
||||||
|
"lua",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("core", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/core.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (dependencyList) |depName| {
|
||||||
|
const dep = b.dependency(depName, .{ .target = target, .optimize = optimize });
|
||||||
|
mod.addImport(depName, dep.module(depName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_step = b.step("test", "run unit tests for core");
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("tests/tests.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.root_module.addImport("core", mod);
|
||||||
|
const runArtifact = b.addRunArtifact(tests);
|
||||||
|
test_step.dependOn(&runArtifact.step);
|
||||||
|
b.installArtifact(tests);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
.{
|
||||||
|
.name = "core",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.spng = .{ .path = "../../lib/spng" },
|
||||||
|
.nfd = .{ .path = "../../lib/nfd" },
|
||||||
|
.p2 = .{ .path = "../../lib/p2" },
|
||||||
|
.tracy = .{ .path = "../../lib/tracy" },
|
||||||
|
.zmath = .{ .path = "../../lib/zmath" },
|
||||||
|
.packer = .{ .path = "../../lib/packer" },
|
||||||
|
.lua = .{ .path = "../../lib/lua" },
|
||||||
|
.zgltf = .{ .path = "../../lib/zgltf" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// TODO --- this should become it's own engine library?
|
||||||
|
const std = @import("std");
|
||||||
|
const spng = @import("spng");
|
||||||
|
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
pub const PngContents = struct {
|
||||||
|
path: []const u8,
|
||||||
|
pixels: []u8,
|
||||||
|
size: core.Vector2u,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, filePath: []const u8) !@This() {
|
||||||
|
const pngFileContents = try core.loadFileAlloc(filePath, 1, allocator);
|
||||||
|
defer allocator.free(pngFileContents);
|
||||||
|
var decoder = try spng.SpngContext.newDecoder();
|
||||||
|
defer decoder.deinit();
|
||||||
|
|
||||||
|
try decoder.setBuffer(pngFileContents);
|
||||||
|
const header = try decoder.getHeader();
|
||||||
|
|
||||||
|
const imageSize = @as(usize, @intCast(header.width * header.height * 4));
|
||||||
|
core.graphics_log("loaded png {s}, dimensions={d}x{d}", .{ filePath, header.width, header.height });
|
||||||
|
const pixels: []u8 = try allocator.alloc(u8, imageSize);
|
||||||
|
const len = try decoder.decode(pixels, spng.SPNG_FMT_RGBA8, spng.SPNG_DECODE_TRNS);
|
||||||
|
try core.assertf(len == pixels.len, "decoded pixel size not buffer size {d} != {d}", .{ len, pixels.len });
|
||||||
|
|
||||||
|
return PngContents{
|
||||||
|
.path = try core.dupe(u8, allocator, filePath),
|
||||||
|
.pixels = pixels,
|
||||||
|
.size = .{ .x = header.width, .y = header.height },
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.allocator.free(self.path);
|
||||||
|
self.allocator.free(self.pixels);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// contains various engine callbacks for other modules to hook into.
|
||||||
|
// these are ALL called on the system thread.
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
onFrameDebugInfoEmitted: std.ArrayListUnmanaged(OnFrameDebugInfoEmitted) = .{},
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.onFrameDebugInfoEmitted.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const engineObject = @import("engineObject.zig");
|
||||||
|
const EngineDataEventError = engineObject.EngineDataEventError;
|
||||||
|
|
||||||
|
// callback definition for a function which updates engine frametime.
|
||||||
|
const OnFrameDebugInfoEmitted = struct {
|
||||||
|
ctx: *anyopaque,
|
||||||
|
func: *const fn (
|
||||||
|
*anyopaque, // active context
|
||||||
|
f64, //
|
||||||
|
) EngineDataEventError!void,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
// memory tracker
|
||||||
|
//
|
||||||
|
// todo.. rename this to memory.zig
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
lock: std.Thread.Mutex = .{},
|
||||||
|
backingAllocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
allocationsCount: u32 = 0,
|
||||||
|
totalAllocSize: usize = 0,
|
||||||
|
eventsCount: usize = 0,
|
||||||
|
|
||||||
|
peakAllocations: u32 = 0,
|
||||||
|
peakAllocSize: usize = 0,
|
||||||
|
|
||||||
|
stackCompactor: ?*core.StackCompactor = undefined,
|
||||||
|
|
||||||
|
timeline: ?EventTimeline,
|
||||||
|
|
||||||
|
//const EventTimeline = core.algorithm.PagedVector(AllocEvent);
|
||||||
|
const EventTimeline = struct {
|
||||||
|
timelineAllocator: std.mem.Allocator,
|
||||||
|
events: core.algorithm.PagedVector(AllocEvent),
|
||||||
|
timestamps: core.algorithm.PagedVector(f64), // timestamp in milliseconds
|
||||||
|
|
||||||
|
pub fn init(timelineAllocator: std.mem.Allocator) !@This() {
|
||||||
|
return .{
|
||||||
|
.timelineAllocator = timelineAllocator,
|
||||||
|
.events = try core.algorithm.PagedVector(AllocEvent).init(timelineAllocator),
|
||||||
|
.timestamps = try core.algorithm.PagedVector(f64).init(timelineAllocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn pushAlloc(self: *@This(), compactor: *core.StackCompactor, ptr: usize, size: usize) void {
|
||||||
|
const callstackId = compactor.getCallStack();
|
||||||
|
self.timestamps.append(core.getEngineTime()) catch unreachable;
|
||||||
|
self.events.append(.{
|
||||||
|
.callstackId = callstackId,
|
||||||
|
.address = ptr,
|
||||||
|
.event = .{ .alloc = .{ .size = size } },
|
||||||
|
}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn pushResize(self: *@This(), compactor: *core.StackCompactor, ptr: usize, old_len: usize, new_len: usize) void {
|
||||||
|
const callstackId = compactor.getCallStack();
|
||||||
|
self.timestamps.append(core.getEngineTime()) catch unreachable;
|
||||||
|
self.events.append(.{
|
||||||
|
.callstackId = callstackId,
|
||||||
|
.address = ptr,
|
||||||
|
.event = .{ .resize = .{ .oldSize = old_len, .newSize = new_len } },
|
||||||
|
}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn pushFree(self: *@This(), compactor: *core.StackCompactor, ptr: usize, size: usize) void {
|
||||||
|
const callstackId = compactor.getCallStack();
|
||||||
|
self.timestamps.append(core.getEngineTime()) catch unreachable;
|
||||||
|
self.events.append(.{
|
||||||
|
.callstackId = callstackId,
|
||||||
|
.address = ptr,
|
||||||
|
.event = .{ .free = .{ .size = size } },
|
||||||
|
}) catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// warning, due to laziness this uses timelineAllocator, which will leak
|
||||||
|
pub fn dumpTimeline(filename: []const u8) !void {
|
||||||
|
if (MTGet()) |tracker| {
|
||||||
|
tracker.lock.lock();
|
||||||
|
defer tracker.lock.unlock();
|
||||||
|
if (tracker.stackCompactor) |compactor| {
|
||||||
|
if (tracker.timeline) |timeline| {
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const ofile = try std.fmt.allocPrint(timeline.timelineAllocator, core.DefaultSavePath ++ "/{s}", .{filename});
|
||||||
|
defer timeline.timelineAllocator.free(ofile);
|
||||||
|
|
||||||
|
var obuf = std.ArrayList(u8).init(timeline.timelineAllocator);
|
||||||
|
defer obuf.deinit();
|
||||||
|
var writer = obuf.writer();
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
|
||||||
|
while (i < timeline.events.len()) : (i += 1) {
|
||||||
|
const event = timeline.events.get(i);
|
||||||
|
const timestamp = timeline.timestamps.get(i);
|
||||||
|
|
||||||
|
try writer.print("{d}: callstack: {x} @0x{x} ", .{
|
||||||
|
timestamp.*,
|
||||||
|
event.callstackId,
|
||||||
|
event.address,
|
||||||
|
});
|
||||||
|
switch (event.event) {
|
||||||
|
.alloc => |x| {
|
||||||
|
try writer.print("alloc {d} bytes\n", .{x.size});
|
||||||
|
},
|
||||||
|
.free => |x| {
|
||||||
|
try writer.print("free {d} bytes\n", .{x.size});
|
||||||
|
},
|
||||||
|
.resize => |x| {
|
||||||
|
try writer.print("resize {d} -> {d} bytes\n", .{ x.oldSize, x.newSize });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var iter = compactor.stackMap.iterator();
|
||||||
|
while (iter.next()) |x| {
|
||||||
|
const callstackId = x.key_ptr.*;
|
||||||
|
const stack = x.value_ptr.*;
|
||||||
|
try writer.print("{x}:\n", .{callstackId});
|
||||||
|
for (stack.debugStr) |frame| {
|
||||||
|
if (frame) |f| {
|
||||||
|
try writer.print("{s}\n", .{f});
|
||||||
|
} else {
|
||||||
|
try writer.print("no file\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try cwd.makePath(core.DefaultSavePath);
|
||||||
|
try cwd.writeFile(.{
|
||||||
|
.sub_path = ofile,
|
||||||
|
.data = obuf.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.engine_logs("skipping writing memory timeline as timeline is not enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AllocEventType = enum { alloc, free, resize };
|
||||||
|
|
||||||
|
const AllocEvent = struct {
|
||||||
|
callstackId: u32,
|
||||||
|
address: usize,
|
||||||
|
event: union(AllocEventType) {
|
||||||
|
alloc: struct { size: usize },
|
||||||
|
free: struct { size: usize },
|
||||||
|
resize: struct { oldSize: usize, newSize: usize },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var vtable: std.mem.Allocator.VTable = .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.free = free,
|
||||||
|
.resize = resize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(backingAllocator: std.mem.Allocator, settings: SetupSettings) @This() {
|
||||||
|
// use the ansi allocator
|
||||||
|
|
||||||
|
const EnableMemoryTimeline = settings.timeline and builtin.os.tag == .windows;
|
||||||
|
|
||||||
|
if (EnableMemoryTimeline) {
|
||||||
|
core.engine_logs("[dmt] Enabling Detailed Memory Tracking");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackCompactor = if (EnableMemoryTimeline) core.StackCompactor.create(std.heap.c_allocator) catch null else null;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.backingAllocator = backingAllocator,
|
||||||
|
.stackCompactor = stackCompactor,
|
||||||
|
.timeline = if (EnableMemoryTimeline) EventTimeline.init(std.heap.c_allocator) catch null else null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *@This()) std.mem.Allocator {
|
||||||
|
return .{
|
||||||
|
.ptr = self,
|
||||||
|
.vtable = &vtable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
|
||||||
|
var self: *@This() = @alignCast(@ptrCast(ctx));
|
||||||
|
const rv = self.backingAllocator.vtable.alloc(self.backingAllocator.ptr, len, ptr_align, ret_addr);
|
||||||
|
|
||||||
|
{
|
||||||
|
self.lock.lock();
|
||||||
|
defer self.lock.unlock();
|
||||||
|
self.allocationsCount += 1;
|
||||||
|
self.totalAllocSize += len;
|
||||||
|
self.eventsCount += 1;
|
||||||
|
|
||||||
|
if (self.totalAllocSize > self.peakAllocSize) {
|
||||||
|
self.peakAllocSize = self.totalAllocSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.allocationsCount > self.peakAllocations) {
|
||||||
|
self.peakAllocations = self.allocationsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.timeline) |*timeline| {
|
||||||
|
timeline.pushAlloc(self.stackCompactor.?, @intFromPtr(rv), len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
|
||||||
|
var self: *@This() = @alignCast(@ptrCast(ctx));
|
||||||
|
{
|
||||||
|
self.lock.lock();
|
||||||
|
defer self.lock.unlock();
|
||||||
|
self.totalAllocSize = self.totalAllocSize - buf.len + new_len;
|
||||||
|
self.eventsCount += 1;
|
||||||
|
|
||||||
|
if (self.timeline) |*timeline| {
|
||||||
|
timeline.pushResize(self.stackCompactor.?, @intFromPtr(buf.ptr), buf.len, new_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.backingAllocator.vtable.resize(
|
||||||
|
self.backingAllocator.ptr,
|
||||||
|
buf,
|
||||||
|
buf_align,
|
||||||
|
new_len,
|
||||||
|
ret_addr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
|
||||||
|
var self: *@This() = @alignCast(@ptrCast(ctx));
|
||||||
|
{
|
||||||
|
self.lock.lock();
|
||||||
|
defer self.lock.unlock();
|
||||||
|
|
||||||
|
self.allocationsCount -= 1;
|
||||||
|
self.totalAllocSize -= buf.len;
|
||||||
|
self.eventsCount += 1;
|
||||||
|
|
||||||
|
if (self.timeline) |*timeline| {
|
||||||
|
timeline.pushFree(self.stackCompactor.?, @intFromPtr(buf.ptr), buf.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.backingAllocator.vtable.free(self.backingAllocator.ptr, buf, buf_align, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printStats(self: @This()) void {
|
||||||
|
std.debug.print("allocations: {d}\n", .{self.allocationsCount});
|
||||||
|
std.debug.print("memory committed: {d}\n", .{self.totalAllocSize});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addUntrackedAllocation(self: *@This(), allocatedSize: usize) void {
|
||||||
|
self.totalAllocSize += allocatedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeUntrackedAllocation(self: *@This(), allocatedSize: usize) void {
|
||||||
|
self.totalAllocSize -= allocatedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gMemTracker: ?*@This() = null;
|
||||||
|
|
||||||
|
pub const SetupSettings = struct {
|
||||||
|
timeline: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn MTSetup(backingAllocator: std.mem.Allocator, settings: SetupSettings) void {
|
||||||
|
gMemTracker = backingAllocator.create(@This()) catch unreachable;
|
||||||
|
gMemTracker.?.* = @This().init(backingAllocator, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn MTShutdown() void {
|
||||||
|
var backingAllocator = gMemTracker.?.backingAllocator;
|
||||||
|
gMemTracker.?.deinit();
|
||||||
|
backingAllocator.destroy(gMemTracker.?);
|
||||||
|
gMemTracker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn MTGet() ?*@This() {
|
||||||
|
return gMemTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo replace all these functions with a virtual table
|
||||||
|
pub fn MTAddUntrackedAllocation(allocatedSize: usize) void {
|
||||||
|
if (gMemTracker) |mt|
|
||||||
|
mt.addUntrackedAllocation(allocatedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn MTRemoveAllocation(allocatedSize: usize) void {
|
||||||
|
if (gMemTracker) |mt|
|
||||||
|
mt.removeUntrackedAllocation(allocatedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn MTPrintStatsDelta() void {
|
||||||
|
if (gMemTracker) |mt| {
|
||||||
|
core.engine_log("allocated size: {d} ({d:.3} MiB)", .{
|
||||||
|
mt.totalAllocSize,
|
||||||
|
@as(f64, @floatFromInt(mt.totalAllocSize)) / 1024 / 1024,
|
||||||
|
});
|
||||||
|
core.engine_log("peak allocated size size: {d} ({d:.3} MiB) ({d} peak allocations)", .{
|
||||||
|
mt.peakAllocSize,
|
||||||
|
@as(f64, @floatFromInt(mt.peakAllocSize)) / 1024 / 1024,
|
||||||
|
mt.peakAllocations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn PrintStatsWithTag(comptime tag: []const u8) void {
|
||||||
|
core.engine_logs(tag);
|
||||||
|
MTPrintStatsDelta();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
// CLI argument parsing library
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn ParseArgs(comptime T: type) !T {
|
||||||
|
// I think i am actually totally cool with leaking this one and leaving it around... just dont spam call this function
|
||||||
|
var iter = try std.process.argsWithAllocator(std.heap.c_allocator);
|
||||||
|
var args: T = .{};
|
||||||
|
|
||||||
|
const shortBuf: [32]u8 = undefined;
|
||||||
|
_ = shortBuf;
|
||||||
|
|
||||||
|
var longBuf: [256]u8 = undefined;
|
||||||
|
|
||||||
|
while (iter.next()) |a| {
|
||||||
|
inline for (@typeInfo(T).Struct.fields) |field| {
|
||||||
|
const longRef = try std.fmt.bufPrint(&longBuf, "--{s}", .{field.name});
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, a, longRef)) {
|
||||||
|
// check if it has a value argument
|
||||||
|
if (a.len > longRef.len) {
|
||||||
|
if (a[longRef.len] == '=' and a.len > longRef.len + 1) {
|
||||||
|
const right = a[longRef.len + 1 ..];
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.Bool => {
|
||||||
|
if (std.mem.eql(u8, right, "true") or
|
||||||
|
std.mem.eql(u8, right, "t") or
|
||||||
|
std.mem.eql(u8, right, "True") or
|
||||||
|
std.mem.eql(u8, right, "TRUE") or
|
||||||
|
std.mem.eql(u8, right, "T") or
|
||||||
|
std.mem.eql(u8, right, "1"))
|
||||||
|
{
|
||||||
|
@field(args, field.name) = true;
|
||||||
|
} else if (std.mem.eql(u8, right, "false") or
|
||||||
|
std.mem.eql(u8, right, "f") or
|
||||||
|
std.mem.eql(u8, right, "False") or
|
||||||
|
std.mem.eql(u8, right, "FALSE") or
|
||||||
|
std.mem.eql(u8, right, "F") or
|
||||||
|
std.mem.eql(u8, right, "0"))
|
||||||
|
{
|
||||||
|
@field(args, field.name) = false;
|
||||||
|
} else {
|
||||||
|
std.debug.print("Error parsing: non-boolean argument", .{});
|
||||||
|
@field(args, field.name) = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Int => {
|
||||||
|
@field(args, field.name) = try std.fmt.parseInt(field.type, right, 10);
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
@field(args, field.name) = try std.fmt.parseFloat(field.type, right);
|
||||||
|
},
|
||||||
|
.Pointer => |pointer| {
|
||||||
|
switch (pointer.size) {
|
||||||
|
.Slice => {
|
||||||
|
@field(args, field.name) = right;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("only u8 slices are supported");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("unable to generate argparse, unsupported struct type in field: " ++ field.name);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std.debug.print("Error parsing arguments: Expected value after argument assignment '{s}'\n", .{a});
|
||||||
|
return error.MalformedArgument;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.Bool => {
|
||||||
|
@field(args, field.name) = true;
|
||||||
|
},
|
||||||
|
.Int, .Float => {
|
||||||
|
std.debug.print("Error parsing arguments: Expected value '{s}'\n", .{a});
|
||||||
|
return error.MalformedArgument;
|
||||||
|
},
|
||||||
|
.Pointer => |pointer| {
|
||||||
|
switch (pointer.size) {
|
||||||
|
.Slice => {
|
||||||
|
std.debug.print("Error parsing arguments: Expected value '{s}'\n", .{a});
|
||||||
|
return error.MalformedArgument;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("only u8 slices are supported");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("unable to generate argparse, unsupported struct type in field: " ++ field.name);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const A = struct {
|
||||||
|
foo: f32 = 0,
|
||||||
|
bar: i32 = 0,
|
||||||
|
baz: bool = false,
|
||||||
|
test_file: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
var iter = std.process.args();
|
||||||
|
const args = try ParseArgs(A, &iter);
|
||||||
|
|
||||||
|
std.debug.print("A: {any}", .{args});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "test args" {
|
||||||
|
const A = struct {
|
||||||
|
foo: f32 = 0,
|
||||||
|
bar: i32 = 0,
|
||||||
|
baz: bool = false,
|
||||||
|
test_file: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const args = try ParseArgs(A, &.{ "--foo=32", "--bar=22", "--baz", "--test_file=lmao2nova" });
|
||||||
|
|
||||||
|
std.debug.assert(args.foo == 32);
|
||||||
|
std.debug.assert(args.bar == 22);
|
||||||
|
std.debug.assert(args.baz == true);
|
||||||
|
std.debug.assert(std.mem.eql(u8, args.test_file, "lmao2nova"));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// RGBA format for color
|
||||||
|
pub const Color32 = u32;
|
||||||
|
|
||||||
|
pub const ColorRGBA8 = extern struct {
|
||||||
|
r: u8 = 0x0,
|
||||||
|
g: u8 = 0x0,
|
||||||
|
b: u8 = 0x0,
|
||||||
|
a: u8 = 0xff,
|
||||||
|
|
||||||
|
pub fn fromHex(hex: u32) @This() {
|
||||||
|
return .{
|
||||||
|
.r = @as(u8, @intCast((hex >> 24) & 0xFF)),
|
||||||
|
.g = @as(u8, @intCast((hex >> 16) & 0xFF)),
|
||||||
|
.b = @as(u8, @intCast((hex >> 8) & 0xFF)),
|
||||||
|
.a = @as(u8, @intCast((hex) & 0xFF)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromColor(o: Color) @This() {
|
||||||
|
return .{
|
||||||
|
.r = @as(u8, @intFromFloat(std.math.clamp(o.r, 0, 1.0) * 255)),
|
||||||
|
.g = @as(u8, @intFromFloat(std.math.clamp(o.g, 0, 1.0) * 255)),
|
||||||
|
.b = @as(u8, @intFromFloat(std.math.clamp(o.b, 0, 1.0) * 255)),
|
||||||
|
.a = @as(u8, @intFromFloat(std.math.clamp(o.a, 0, 1.0) * 255)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Color = extern struct {
|
||||||
|
r: f32 = 0,
|
||||||
|
g: f32 = 0,
|
||||||
|
b: f32 = 0,
|
||||||
|
a: f32 = 1.0,
|
||||||
|
|
||||||
|
pub const White = fromRGB(0xFFFFFF);
|
||||||
|
pub const Red = fromRGB(0xFF0000);
|
||||||
|
pub const Yellow = fromRGB(0xFFFF00);
|
||||||
|
pub const Orange = fromRGB(0xFF5500);
|
||||||
|
pub const Green = fromRGB(0x00FF00);
|
||||||
|
pub const Blue = fromRGB(0x0000FF);
|
||||||
|
pub const Cyan = fromRGB(0x00FFFF);
|
||||||
|
pub const Magenta = fromRGB(0xFF00FF);
|
||||||
|
pub const Black = fromRGB(0x000000);
|
||||||
|
|
||||||
|
pub fn intoRGBA(self: @This()) Color32 {
|
||||||
|
return ((@as(u32, @intFromFloat(self.r)) * 0xFF) << 24) |
|
||||||
|
((@as(u32, @intFromFloat(self.g)) & 0xFF) << 16) |
|
||||||
|
((@as(u32, @intFromFloat(self.b)) & 0xFF) << 8) |
|
||||||
|
((@as(u32, @intFromFloat(self.a)) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromRGB2(r: anytype, g: anytype, b: anytype) @This() {
|
||||||
|
return @This(){ .r = r, .g = g, .b = b, .a = 1.0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromRGBA2(r: anytype, g: anytype, b: anytype, a: anytype) @This() {
|
||||||
|
return @This(){ .r = r, .g = g, .b = b, .a = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromRGB(rgb: u32) @This() {
|
||||||
|
return @This(){
|
||||||
|
.r = @as(f32, @floatFromInt((rgb >> 16) & 0xFF)) / 255,
|
||||||
|
.g = @as(f32, @floatFromInt((rgb >> 8) & 0xFF)) / 255,
|
||||||
|
.b = @as(f32, @floatFromInt((rgb) & 0xFF)) / 255,
|
||||||
|
.a = 1.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromRGBA(rgba: u32) @This() {
|
||||||
|
return @This(){
|
||||||
|
.r = @as(f32, @floatFromInt((rgba >> 24) & 0xFF)) / 255,
|
||||||
|
.g = @as(f32, @floatFromInt((rgba >> 16) & 0xFF)) / 255,
|
||||||
|
.b = @as(f32, @floatFromInt((rgba >> 8) & 0xFF)) / 255,
|
||||||
|
.a = @as(f32, @floatFromInt((rgba) & 0xFF)) / 255,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
// main public facing root file for core
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const inputs = @import("inputs/inputStack.zig");
|
||||||
|
pub const getInputStack = inputs.getInputStack;
|
||||||
|
pub const ActionEvent = inputs.ActionEvent;
|
||||||
|
pub const Axis1dBinding = inputs.Axis1dBinding;
|
||||||
|
pub const Axis2dBinding = inputs.Axis2dBinding;
|
||||||
|
pub const ActionBinding = inputs.ActionBinding;
|
||||||
|
pub const IOEvent = inputs.IOEvent;
|
||||||
|
|
||||||
|
pub const debug_draw = @import("debug_draw.zig");
|
||||||
|
pub const DebugDrawParams = debug_draw.DebugDrawParams;
|
||||||
|
pub const DebugDrawInterface = debug_draw.DebugDrawInterface;
|
||||||
|
pub const installDebugDrawInterface = debug_draw.installDebugDrawInterface;
|
||||||
|
pub const debugSphere = debug_draw.debugSphere;
|
||||||
|
pub const debugSphereTransform = debug_draw.debugSphereTransform;
|
||||||
|
pub const debugBox = debug_draw.debugBox;
|
||||||
|
pub const debugLine = debug_draw.debugLine;
|
||||||
|
pub const script_bindings = script.script_bindings;
|
||||||
|
|
||||||
|
pub usingnamespace @import("misc.zig");
|
||||||
|
pub usingnamespace @import("logging.zig");
|
||||||
|
pub usingnamespace @import("engineTime.zig");
|
||||||
|
pub usingnamespace @import("engineObject.zig");
|
||||||
|
pub usingnamespace @import("jobs.zig");
|
||||||
|
pub usingnamespace @import("file_utils.zig");
|
||||||
|
pub usingnamespace @import("string_utils.zig");
|
||||||
|
pub usingnamespace @import("type_utils.zig");
|
||||||
|
pub const engine = @import("engine.zig");
|
||||||
|
pub const tracy = @import("tracy");
|
||||||
|
pub const png = @import("png.zig");
|
||||||
|
|
||||||
|
pub const colors = @import("colors.zig");
|
||||||
|
|
||||||
|
pub const zgltf = @import("zgltf");
|
||||||
|
|
||||||
|
pub const zm = @import("zmath");
|
||||||
|
pub usingnamespace @import("p2");
|
||||||
|
pub const algorithm = @import("p2");
|
||||||
|
pub const nfd = @import("nfd");
|
||||||
|
pub usingnamespace @import("math.zig");
|
||||||
|
pub usingnamespace @import("args.zig");
|
||||||
|
pub usingnamespace @import("file_dialogue.zig");
|
||||||
|
|
||||||
|
pub const panickers = @import("panickers.zig");
|
||||||
|
pub const scene = @import("scene.zig");
|
||||||
|
pub const SceneSystem = scene.SceneSystem;
|
||||||
|
|
||||||
|
pub const Engine = engine.Engine;
|
||||||
|
|
||||||
|
const Name = algorithm.Name;
|
||||||
|
pub const spng = @import("spng");
|
||||||
|
|
||||||
|
pub const MemoryTracker = @import("MemoryTracker.zig");
|
||||||
|
|
||||||
|
pub const DefaultSavePath = "Saved";
|
||||||
|
|
||||||
|
const logging = @import("logging.zig");
|
||||||
|
const c = @This();
|
||||||
|
|
||||||
|
const logs = logging.engine_logs;
|
||||||
|
const log = logging.engine_log;
|
||||||
|
|
||||||
|
pub const packer = @import("packer");
|
||||||
|
|
||||||
|
pub const FileSystem = packer.PackerFS;
|
||||||
|
const PackerFS = packer.PackerFS;
|
||||||
|
|
||||||
|
pub const StackCompactor = stacks.StackCompactor;
|
||||||
|
|
||||||
|
var gPackerFS: *PackerFS = undefined;
|
||||||
|
|
||||||
|
pub var gScene: *SceneSystem = undefined;
|
||||||
|
pub const Scene = scene.Scene;
|
||||||
|
|
||||||
|
pub const ecs = @import("ecs.zig");
|
||||||
|
pub usingnamespace ecs;
|
||||||
|
|
||||||
|
pub const script = @import("script.zig");
|
||||||
|
|
||||||
|
pub const stacks = @import("stacks.zig");
|
||||||
|
pub const walkAndPrintStack = stacks.walkAndPrintStack;
|
||||||
|
|
||||||
|
pub fn fs() *PackerFS {
|
||||||
|
return gPackerFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Module = ModuleDescription{
|
||||||
|
.name = "core",
|
||||||
|
.enabledByDefault = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var gIsUtility: bool = false;
|
||||||
|
|
||||||
|
pub fn isUtility() bool {
|
||||||
|
return gIsUtility;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkArgBool(args: anytype, comptime field: []const u8) bool {
|
||||||
|
if (@hasField(@TypeOf(args), "fatDump")) {
|
||||||
|
return @field(args, field);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void {
|
||||||
|
if (@hasField(@TypeOf(programSpec), "utility")) {
|
||||||
|
logs("utility mode - no gui");
|
||||||
|
gIsUtility = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fatDump: bool = false;
|
||||||
|
if (checkArgBool(args, "fatDump")) {
|
||||||
|
fatDump = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
script.lua.setupMiniDump(fatDump);
|
||||||
|
_ = try algorithm.createNameRegistry(allocator);
|
||||||
|
// LUA BEGIN -- what if i want to make the scripting integration optional?
|
||||||
|
try script.start_lua(allocator);
|
||||||
|
// LUA END
|
||||||
|
gPackerFS = try PackerFS.init(allocator, .{});
|
||||||
|
gEngine = try allocator.create(Engine);
|
||||||
|
gEngine.* = try Engine.init(allocator);
|
||||||
|
|
||||||
|
if (@hasField(@TypeOf(args), "unitTest") and args.unitTest) {} else {
|
||||||
|
try logging.setupLogging(gEngine);
|
||||||
|
}
|
||||||
|
|
||||||
|
try ecs.setup(allocator);
|
||||||
|
|
||||||
|
gScene = try gEngine.createObject(scene.SceneSystem, .{ .can_tick = true });
|
||||||
|
|
||||||
|
try algorithm.string_pool.setup(allocator);
|
||||||
|
|
||||||
|
_ = try gEngine.createObject(script_bindings.ScriptTicks, .{ .can_tick = true });
|
||||||
|
|
||||||
|
_ = try inputs.initInputStack();
|
||||||
|
|
||||||
|
logs("core module starting up... ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_module(_: std.mem.Allocator) void {
|
||||||
|
logs("core module shutting down...");
|
||||||
|
|
||||||
|
MemoryTracker.MTPrintStatsDelta();
|
||||||
|
|
||||||
|
logging.shutdownLogging();
|
||||||
|
ecs.shutdown();
|
||||||
|
|
||||||
|
debug_draw.shutdownDrawInterface();
|
||||||
|
algorithm.destroyNameRegistry();
|
||||||
|
gEngine.deinit();
|
||||||
|
gPackerFS.destroy();
|
||||||
|
// LUA BEGIN
|
||||||
|
script.shutdown_lua();
|
||||||
|
// LUA END
|
||||||
|
algorithm.string_pool.shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatchJob(capture: anytype) !void {
|
||||||
|
try gEngine.jobManager.newJob(capture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub var gEngine: *Engine = undefined;
|
||||||
|
|
||||||
|
pub fn createObject(comptime T: type, params: engine.NeonObjectParams) !*T {
|
||||||
|
return gEngine.createObject(T, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setupEnginePlatform(ctx: *anyopaque, poll: engine.PollFuncFn, proc: engine.ProcEventsFn) void {
|
||||||
|
gEngine.platformPollFunc = poll;
|
||||||
|
gEngine.platformCtx = ctx;
|
||||||
|
gEngine.platformProcEventsFunc = proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exitNow() void {
|
||||||
|
signalShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signalShutdown() void {
|
||||||
|
gEngine.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getEngine() *Engine {
|
||||||
|
return gEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn BuildOption(comptime option: []const u8) bool {
|
||||||
|
if (@hasDecl(@import("root"), "options")) {
|
||||||
|
const r = @import("root").options;
|
||||||
|
if (@hasDecl(r, option)) {
|
||||||
|
return @field(r, option);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EngineDelegates = @import("EngineDelegates.zig");
|
||||||
|
|
||||||
|
// binds a function + a context to
|
||||||
|
pub fn addEngineDelegateBinding(comptime event: []const u8, func: anytype, ctx: *anyopaque) !usize {
|
||||||
|
if (!@hasField(EngineDelegates, event)) {
|
||||||
|
@compileError("Engine does not have delegate " ++ event);
|
||||||
|
}
|
||||||
|
const handle = @field(gEngine.delegates, event).items.len;
|
||||||
|
try @field(gEngine.delegates, event).append(gEngine.delegates.allocator, .{ .func = func, .ctx = ctx });
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEngineTime = @import("engineTime.zig").getEngineTime;
|
||||||
|
|
||||||
|
pub fn getEngineUptime() f64 {
|
||||||
|
return getEngineTime() - gEngine.engineStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const modules = @import("modules.zig");
|
||||||
|
pub const isModuleEnabled = modules.isModuleEnabled;
|
||||||
|
pub const ModuleDescription = modules.ModuleDescription;
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
pub const DebugDrawParams = struct {
|
||||||
|
color: core.Vectorf = .{ .x = 0, .y = 1.0, .z = 0 },
|
||||||
|
duration: f32 = 0,
|
||||||
|
rotation: core.Quat = .{ 0, 0, 0, 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebugDrawInterface = struct {
|
||||||
|
debugSphereFn: *const fn (pos: core.Vectorf, radius: f32, DebugDrawParams) void,
|
||||||
|
debugBoxFn: *const fn (pos: core.Vectorf, extents: core.Vectorf, DebugDrawParams) void,
|
||||||
|
debugLineFn: *const fn (start: core.Vectorf, end: core.Vectorf, DebugDrawParams) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
var gDebugDrawInterface: ?*DebugDrawInterface = null;
|
||||||
|
var gDebugDrawAllocator: ?std.mem.Allocator = null;
|
||||||
|
|
||||||
|
pub fn debugSphereTransform(t: core.Mat, radius: f32, params: DebugDrawParams) void {
|
||||||
|
if (gDebugDrawInterface) |i| {
|
||||||
|
i.debugSphereFn(core.Vectorf.fromZm(core.zm.mul(core.zm.Vec{ 0, 0, 0, 1 }, t)), radius, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugSphere(position: core.Vectorf, radius: f32, params: DebugDrawParams) void {
|
||||||
|
if (gDebugDrawInterface) |i| {
|
||||||
|
i.debugSphereFn(position, radius, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugLine(start: core.Vectorf, end: core.Vectorf, params: DebugDrawParams) void {
|
||||||
|
if (gDebugDrawInterface) |i| {
|
||||||
|
i.debugLineFn(start, end, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugBox(pos: core.Vectorf, extents: core.Vectorf, params: DebugDrawParams) void {
|
||||||
|
if (gDebugDrawInterface) |i| {
|
||||||
|
i.debugBoxFn(pos, extents, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn installDebugDrawInterface(allocator: std.mem.Allocator, newInterface: DebugDrawInterface) !void {
|
||||||
|
gDebugDrawInterface = try allocator.create(DebugDrawInterface);
|
||||||
|
gDebugDrawAllocator = allocator;
|
||||||
|
|
||||||
|
if (gDebugDrawInterface) |interface| {
|
||||||
|
interface.* = newInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdownDrawInterface() void {
|
||||||
|
if (gDebugDrawInterface) |interface| {
|
||||||
|
gDebugDrawAllocator.?.destroy(interface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,411 @@
|
||||||
|
var gEcsRegistry: *EcsRegistry = undefined;
|
||||||
|
|
||||||
|
// wew lad. this is definitely an exploratory implementation I think.
|
||||||
|
//
|
||||||
|
// todo: cruft
|
||||||
|
// - componentregistration.zig should contain all the code having to do with lua registration
|
||||||
|
// - entities should be under ecs/Entities.zig
|
||||||
|
//
|
||||||
|
// ECS: Entities, Containers, Systems
|
||||||
|
//
|
||||||
|
// So this is going to be a bit different thatn flecs or other more mainstream systems. You should think of
|
||||||
|
// this ecs system as more of a way to implement systems which can talk to each other on a level playing ground.
|
||||||
|
//
|
||||||
|
// My world view of how game systems should talk to each other is more like small processes, and the game state
|
||||||
|
// should be a collection of tiny databases that each contain discrete bits of information that piece together
|
||||||
|
// to produce a coherent vision of the world.
|
||||||
|
//
|
||||||
|
// Everything that should be tracked through ECS should be something that is gameplay significant.
|
||||||
|
// To this end, ecs has a maximum entity count of 1 million unique entities.
|
||||||
|
//
|
||||||
|
// This should be decent enough to pretty much implement 99% of features in th ecs features.
|
||||||
|
//
|
||||||
|
// However features which require a large amount of entities is likely a bad idea and not something that
|
||||||
|
// should be done in ecs.
|
||||||
|
//
|
||||||
|
// Eg. you wouldn't do a particle system in ecs and have each transform be a unique entry in the transforms
|
||||||
|
// sparse set.
|
||||||
|
//
|
||||||
|
// But you could conceivably do projectiles.
|
||||||
|
//
|
||||||
|
// Overall though you need to ask yourself. If something needs to be an entity or not. entities exist to
|
||||||
|
// recieve messages. Not nessecarily even send messages.
|
||||||
|
//
|
||||||
|
// The base implementation ideas are mostly solid but the level of cruft involed in scripting and ecs is a bit
|
||||||
|
// nasty at this time bear with me.
|
||||||
|
//
|
||||||
|
// Ok one mental model I think I can with entities is that each entity can be thought of as an independent object
|
||||||
|
// that contains no state.
|
||||||
|
//
|
||||||
|
// When it needs to actually do something, it gets added to a container and one or more systems now have a way
|
||||||
|
// to address the entity.
|
||||||
|
//
|
||||||
|
// systems that would be present for a character in a first person shooter for example
|
||||||
|
//
|
||||||
|
// --- game systems --- ones that the game would write and implement
|
||||||
|
//
|
||||||
|
// ( one or none of these three )
|
||||||
|
// player: recieves input messages from local input, forwards it to controller
|
||||||
|
// netplayer: recieves input messages from the network driver, forwards it to controller
|
||||||
|
// botplayer: generates control messages which drive bots and gameplay AI. forwards it to an controller
|
||||||
|
// - could use a behaviour tree under the hood
|
||||||
|
// arms: takes input actions and runs scripts to implement things that would be in front of the camera.
|
||||||
|
// - arms, weapons, etc...
|
||||||
|
// controller: converts raw player input to movement input for the character movement and other subsystems
|
||||||
|
// - eg. converts input axis to a vector movement
|
||||||
|
// - converts 'mouse_click_1' to a weapon fire call on the weapon component
|
||||||
|
// - also converts actions from the botplayer component which may send special components
|
||||||
|
// character: stores general gameplay relevant information about a given character.
|
||||||
|
// - gameplay stuff, updates values on the character_movement etc.. controls states, such as ragdolling
|
||||||
|
// character_movement: reconciles physics component data and movement coming in from controls
|
||||||
|
//
|
||||||
|
// --- engine systems --- ones that would come with the engine
|
||||||
|
// camera: Can be set as active. has a global state which tracks which camera is active
|
||||||
|
// physics: integration with the physics engine.
|
||||||
|
// animationGraph: animation graph drives skeletons inside the rendering_component
|
||||||
|
// renderScene: contains a list of subobjects which describe the visual-only scene for this character
|
||||||
|
// - renderScene will be the most complex one, lighter weight variants will include
|
||||||
|
// - staticMesh
|
||||||
|
// transform: final position of the character
|
||||||
|
// netAddress: acts as an endpoint to recieve network messages to this specific entity
|
||||||
|
//
|
||||||
|
// --- another thought experiment ----
|
||||||
|
// systems present in a mazing tower defense that is networked.
|
||||||
|
//
|
||||||
|
// game:
|
||||||
|
// mobs:
|
||||||
|
// mr/mazing:
|
||||||
|
// I think just this one system can handle the whole damn thing for enemies
|
||||||
|
// has a reference to the pathfinder entity. Which finds the best path for each enemy type to reach the
|
||||||
|
// main tower.
|
||||||
|
// mr/attributes:
|
||||||
|
// lightweight attributes which includes a buff/debuff system internally
|
||||||
|
// mr/pathfinder (singleton:):
|
||||||
|
// system which calls the pathfinding system to generate path segments for each of the segments of the map.
|
||||||
|
// can be queried based on position to tell the entity where to go next. includes an internal set of waypoints
|
||||||
|
//
|
||||||
|
// engine: systems that come with the engine
|
||||||
|
// p3/: peter's gameplay implementation toolbox.
|
||||||
|
// p3/grid_pathfinding: A* based pathfinding system, can create multiple grids and stitch them together.
|
||||||
|
//
|
||||||
|
// this is the kind of workflows I want to enable with ecs.
|
||||||
|
//
|
||||||
|
// - creating entities and associating that entity with any arbitrary number of systems
|
||||||
|
// - entities
|
||||||
|
//
|
||||||
|
// entities are globally unique ID handles. when you create an entity. you do so by basically asking
|
||||||
|
// the core system what set handle is free. this also adds that entity to the central registry
|
||||||
|
// which tracks stuff like if the object is completely free'd or not
|
||||||
|
//
|
||||||
|
// components which are a part of an entity are nothing more than an entry in another data structure called a container
|
||||||
|
// Any data structure can be used but the main data structures that are available for this purpose are all in
|
||||||
|
// p2/sparse-set.zig
|
||||||
|
//
|
||||||
|
// A few notes about this:
|
||||||
|
// Any data structure can be used as container storage it just needs a wrapper and implement
|
||||||
|
// the EcsContainerInterface found in sparse-set.zig
|
||||||
|
//
|
||||||
|
// current containers are:
|
||||||
|
//
|
||||||
|
// SparseMap (general purpose default, good enough for pretty much everything)
|
||||||
|
// SparseSet (only used for systsems where every entry gets iterated over every single frame)
|
||||||
|
// SparseMultiSet (AOS version of sparseSet Really specialized, only used for core engine systems)
|
||||||
|
|
||||||
|
pub fn createEntity() !Entity {
|
||||||
|
return .{ .handle = try gEcsRegistry.baseSet.createObject(.{}) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn CreateEntity_Lua(state: lua.LuaState) i32 {
|
||||||
|
const ud = state.newZigUserdata(Entity) catch return 0;
|
||||||
|
ud.* = createEntity() catch {
|
||||||
|
std.debug.print("failed to create entity", .{});
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
_ = state.getGlobal("__RegisterEntityProperty");
|
||||||
|
_ = state.pushValue(-2) catch return 0;
|
||||||
|
core.engine_log("Entity created: 0x{x}", .{ud.handle.index});
|
||||||
|
_ = state.pcallStack(1) catch {
|
||||||
|
std.debug.print("faield to exectute registration function", .{});
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(allocator: std.mem.Allocator) !void {
|
||||||
|
try ComponentRef.setupFormatBuffer(allocator);
|
||||||
|
gEcsRegistry = try core.createObject(EcsRegistry, .{ .can_tick = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown() void {
|
||||||
|
ComponentRef.shutdownFormatBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRegistry() *EcsRegistry {
|
||||||
|
return gEcsRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerEcsContainer(ref: EcsContainerRef, name: core.Name) !void {
|
||||||
|
try gEcsRegistry.registerContainer(ref, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deregisterEcsContainer(ref: EcsContainerRef) void {
|
||||||
|
_ = ref;
|
||||||
|
@panic("not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createSystem(comptime System: type, allocator: std.mem.Allocator) !*System {
|
||||||
|
const system = try System.create(allocator);
|
||||||
|
const ref = p2.refFromPtr(EcsSystemInterface, system);
|
||||||
|
core.engine_log("ptr = {any}", .{ref.vtable.tick});
|
||||||
|
|
||||||
|
try gEcsRegistry.systems.append(gEcsRegistry.allocator, ref);
|
||||||
|
if (ref.vtable.tick != null) {
|
||||||
|
try gEcsRegistry.tickableSystems.append(gEcsRegistry.allocator, ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only thing this is meant to do is to provide a central place to construct and destroy objects
|
||||||
|
pub const EcsRegistry = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
baseSet: BaseSet,
|
||||||
|
|
||||||
|
systems: std.ArrayListUnmanaged(EcsSystemRef) = .{},
|
||||||
|
tickableSystems: std.ArrayListUnmanaged(EcsSystemRef) = .{},
|
||||||
|
|
||||||
|
containers: std.ArrayListUnmanaged(EcsContainerRef) = .{},
|
||||||
|
containerNames: std.ArrayListUnmanaged(core.Name) = .{},
|
||||||
|
containersByName: std.AutoHashMapUnmanaged(u32, u32) = .{},
|
||||||
|
|
||||||
|
pub const NeonObjectTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.baseSet = BaseSet.init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerContainer(self: *@This(), ref: EcsContainerRef, _containerName: core.Name) !void {
|
||||||
|
var containerName = _containerName;
|
||||||
|
const newid = self.containers.items.len;
|
||||||
|
|
||||||
|
try self.containers.append(self.allocator, ref);
|
||||||
|
try self.containerNames.append(self.allocator, containerName);
|
||||||
|
try self.containersByName.put(self.allocator, containerName.handle(), @intCast(newid));
|
||||||
|
|
||||||
|
ref.vtable.onRegister(ref.ptr, @intCast(newid), .{
|
||||||
|
.ptr = @ptrCast(self),
|
||||||
|
.onHandleRemoved = onHandleRemoved,
|
||||||
|
.onHandleAdded = onHandleAdded,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onHandleRemoved(p: *anyopaque, containerID: u32, handle: core.ObjectHandle) void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(p));
|
||||||
|
_ = containerID;
|
||||||
|
//core.engine_log("ECS:: object removed id={d} from container={s}({d}) 0x{x}", .{
|
||||||
|
// handle.index,
|
||||||
|
// self.containerNames.items[containerID].utf8(),
|
||||||
|
// containerID,
|
||||||
|
// @intFromPtr(self.containers.items[containerID].ptr),
|
||||||
|
//});
|
||||||
|
|
||||||
|
self.baseSet.get(handle).?.containersCount -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onHandleAdded(p: *anyopaque, containerID: u32, handle: core.ObjectHandle) void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(p));
|
||||||
|
_ = containerID;
|
||||||
|
// core.engine_log("ECS:: object added id=0x{x} from container={s}({d}) 0x{x}", .{
|
||||||
|
// handle.index,
|
||||||
|
// self.containerNames.items[containerID].utf8(),
|
||||||
|
// containerID,
|
||||||
|
// @intFromPtr(self.containers.items[containerID].ptr),
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
if (self.baseSet.get(handle)) |obj| {
|
||||||
|
obj.containersCount += 1;
|
||||||
|
} else {
|
||||||
|
_ = self.baseSet.createWithHandle(handle, .{ .containersCount = 1 }) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), deltaTime: f64) void {
|
||||||
|
if (core.getEngine().isShuttingDown())
|
||||||
|
return;
|
||||||
|
for (self.tickableSystems.items) |ref| {
|
||||||
|
ref.vtable.tick.?(ref.ptr, deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
for (self.containers.items) |ref| {
|
||||||
|
ref.vtable.evictFromRegistry(ref.ptr);
|
||||||
|
}
|
||||||
|
for (self.systems.items) |ref| {
|
||||||
|
ref.vtable.destroy(ref.ptr);
|
||||||
|
}
|
||||||
|
self.systems.deinit(self.allocator);
|
||||||
|
self.tickableSystems.deinit(self.allocator);
|
||||||
|
self.baseSet.deinit();
|
||||||
|
self.containers.deinit(self.allocator);
|
||||||
|
self.containerNames.deinit(self.allocator);
|
||||||
|
self.containersByName.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn defineComponent(comptime Component: type, allocator: std.mem.Allocator) !void {
|
||||||
|
const ContainerType = @TypeOf(Component.BaseContainer.*);
|
||||||
|
Component.BaseContainer = try ContainerType.create(allocator);
|
||||||
|
|
||||||
|
core.engine_log("Component container created " ++ @typeName(Component) ++ " @{x}", .{@intFromPtr(Component.BaseContainer)});
|
||||||
|
const container = makeEcsContainerRef(Component.BaseContainer);
|
||||||
|
try registerEcsContainer(container, core.MakeName(@typeName(Component)));
|
||||||
|
|
||||||
|
try script.registerComponent(Component, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undefineComponent(comptime Component: type) void {
|
||||||
|
Component.BaseContainer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Entity = struct {
|
||||||
|
handle: core.ObjectHandle,
|
||||||
|
|
||||||
|
pub const PodDataTable: pod.DataTable = .{
|
||||||
|
.name = "Entity",
|
||||||
|
.newFuncOverride = lua.CWrap(CreateEntity_Lua),
|
||||||
|
.luaDirectFuncs = &.{
|
||||||
|
.{ .name = "addComponent", .func = "luaAddComponent" },
|
||||||
|
//.{ .name = "get", .func = "luaAddComponent" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn fromHandle(handle: core.ObjectHandle) @This() {
|
||||||
|
return .{ .handle = handle };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addComponent(self: @This(), comptime Component: type) ?*Component {
|
||||||
|
const rv = Component.BaseContainer.createWithHandleECS(self.handle);
|
||||||
|
|
||||||
|
// if (@hasDecl(Component, "init")) {
|
||||||
|
// rv.init(self.handle);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: @This(), comptime Component: type) ?*Component {
|
||||||
|
const rv = Component.BaseContainer.get(self.handle);
|
||||||
|
return if (rv == null) null else @ptrCast(@alignCast(rv.?));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeComponent(self: @This(), comptime Component: type) void {
|
||||||
|
Component.BaseContainer.remove(self.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn luaAddComponent(state: lua.LuaState) i32 {
|
||||||
|
const argc = state.getTop();
|
||||||
|
|
||||||
|
if (argc != 2) {
|
||||||
|
core.engine_log("Add Component Error, expected 2 arguments got {d}", .{argc});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.toUserdata(@This(), 1)) |self| {
|
||||||
|
if (state.toUserdata(ComponentRegistration, 2)) |componentRegistration| {
|
||||||
|
// core.engine_log("component Registration: name: {s}", .{componentRegistration.name});
|
||||||
|
|
||||||
|
// create the zig version of the component
|
||||||
|
const comp = componentRegistration.createComponent(self.handle);
|
||||||
|
|
||||||
|
// call luaNew on ComponentReferenceType
|
||||||
|
// create the lua binding for the component
|
||||||
|
// this pushes one onto the stack
|
||||||
|
componentRegistration.luaNew(state, self.handle, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EcsComponentInterface = p2.MakeInterface("EcsComponentInterfaceVTable", struct {
|
||||||
|
container: ?EcsContainerRef = null,
|
||||||
|
|
||||||
|
pub fn Implement(comptime T: type) @This() {
|
||||||
|
_ = T;
|
||||||
|
const Impl = struct {};
|
||||||
|
_ = Impl;
|
||||||
|
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const EcsContainerInterface = p2.EcsContainerInterface;
|
||||||
|
pub const EcsContainerRef = p2.Reference(EcsContainerInterface);
|
||||||
|
pub fn makeEcsContainerRef(ptr: anytype) EcsContainerRef {
|
||||||
|
return p2.refFromPtr(EcsContainerInterface, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTypeContainer(comptime T: type) EcsContainerRef {
|
||||||
|
return makeEcsContainerRef(T.BaseContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const EcsEntry = struct {
|
||||||
|
containersCount: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BaseSet = p2.SparseSet(EcsEntry);
|
||||||
|
|
||||||
|
pub const EcsSystemRef = p2.Reference(EcsSystemInterface);
|
||||||
|
pub const EcsSystemInterface = p2.MakeInterface("EcsSystemVTable", struct {
|
||||||
|
create: *const fn (std.mem.Allocator) core.EngineDataEventError!*anyopaque,
|
||||||
|
destroy: *const fn (*anyopaque) void,
|
||||||
|
tick: ?*const fn (*anyopaque, f64) void = null,
|
||||||
|
|
||||||
|
pub fn Implement(comptime TargetType: type) @This() {
|
||||||
|
const Wrap = struct {
|
||||||
|
pub fn create(allocator: std.mem.Allocator) core.EngineDataEventError!*anyopaque {
|
||||||
|
const new = try TargetType.create(allocator);
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(p: *anyopaque) void {
|
||||||
|
const ptr: *TargetType = @ptrCast(@alignCast(p));
|
||||||
|
ptr.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(p: *anyopaque, dt: f64) void {
|
||||||
|
const ptr: *TargetType = @ptrCast(@alignCast(p));
|
||||||
|
ptr.tick(dt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return .{
|
||||||
|
.destroy = Wrap.destroy,
|
||||||
|
.create = Wrap.create,
|
||||||
|
.tick = if (@hasDecl(TargetType, "tick")) Wrap.tick else null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ComponentRegistration = @import("script/ComponentRegistration.zig");
|
||||||
|
const ComponentRef = @import("script/ComponentRef.zig");
|
||||||
|
const script = @import("script.zig");
|
||||||
|
const std = @import("std");
|
||||||
|
const p2 = @import("p2");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const lua = @import("lua");
|
||||||
|
const pod = lua.pod;
|
||||||
|
const scene = @import("scene.zig");
|
||||||
|
|
@ -0,0 +1,360 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const logging = @import("logging.zig");
|
||||||
|
const input = @import("input.zig");
|
||||||
|
const engineObject = @import("engineObject.zig");
|
||||||
|
const time = @import("engineTime.zig");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const jobs = @import("jobs.zig");
|
||||||
|
const math = @import("math.zig");
|
||||||
|
|
||||||
|
const tracy = @import("tracy");
|
||||||
|
const p2 = @import("p2");
|
||||||
|
const nfd = @import("nfd");
|
||||||
|
|
||||||
|
const Atomic = std.atomic.Value;
|
||||||
|
const EngineDataEventError = engineObject.EngineDataEventError;
|
||||||
|
|
||||||
|
const Name = p2.Name;
|
||||||
|
const MakeName = p2.MakeName;
|
||||||
|
|
||||||
|
const EngineObjectRef = engineObject.EngineObjectRef;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
||||||
|
const AutoHashMap = std.AutoHashMap;
|
||||||
|
const JobManager = jobs.JobManager;
|
||||||
|
|
||||||
|
const engine_log = logging.engine_log;
|
||||||
|
|
||||||
|
pub const PollFuncFn = *const fn (*anyopaque) EngineDataEventError!void;
|
||||||
|
pub const ProcEventsFn = *const fn (*anyopaque, u64) EngineDataEventError!void;
|
||||||
|
|
||||||
|
const EngineDelegates = @import("EngineDelegates.zig");
|
||||||
|
|
||||||
|
// perhaps a better name for this guy isn't actually engine, rather 'orchestrator' is more apt.
|
||||||
|
// but that's so avant-garde
|
||||||
|
pub const Engine = struct {
|
||||||
|
exitSignal: Atomic(bool) = Atomic(bool).init(false),
|
||||||
|
exitConfirmed: Atomic(bool) = Atomic(bool).init(false),
|
||||||
|
dependentsDestroyed: Atomic(bool) = Atomic(bool).init(false),
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
// better name for these engineObject objects is actually 'engine object'
|
||||||
|
engineObjects: ArrayListUnmanaged(EngineObjectRef),
|
||||||
|
eventors: ArrayListUnmanaged(EngineObjectRef),
|
||||||
|
exitListeners: ArrayListUnmanaged(EngineObjectRef),
|
||||||
|
preTickables: ArrayListUnmanaged(EngineObjectRef),
|
||||||
|
renderers: ArrayListUnmanaged(EngineObjectRef) = .{},
|
||||||
|
tickables: ArrayListUnmanaged(usize), // todo: this maybe should just be a list of objects
|
||||||
|
jobManager: *JobManager,
|
||||||
|
|
||||||
|
// the destroy list is a list of things to destroy when the engine shuts down.
|
||||||
|
// the main difference between core and simple objects, is that when the
|
||||||
|
// simple object shuts down it's an
|
||||||
|
destroyListSimple: ArrayListUnmanaged(EngineObjectRef) = .{},
|
||||||
|
destroyListCore: ArrayListUnmanaged(EngineObjectRef) = .{},
|
||||||
|
|
||||||
|
lastEngineTime: f64,
|
||||||
|
deltaTime: f64, // delta time for this frame from the previous frame
|
||||||
|
frameNumber: u64,
|
||||||
|
|
||||||
|
averageFrameTime: f64 = 0,
|
||||||
|
averageFrameSampleWindow: u32 = 60, // rolling weighted average
|
||||||
|
|
||||||
|
systemsThreadTime: f64 = 0,
|
||||||
|
renderThreadTime: f64 = 0,
|
||||||
|
|
||||||
|
platformCtx: *anyopaque = undefined,
|
||||||
|
platformPollFunc: ?PollFuncFn = null,
|
||||||
|
platformProcEventsFunc: ?ProcEventsFn = null,
|
||||||
|
|
||||||
|
engineStartTime: f64 = 0,
|
||||||
|
|
||||||
|
nfdRuntime: *nfd.NFDRuntime,
|
||||||
|
|
||||||
|
delegates: EngineDelegates,
|
||||||
|
|
||||||
|
first: bool = true,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !@This() {
|
||||||
|
const rv = Engine{
|
||||||
|
.allocator = allocator,
|
||||||
|
.engineObjects = .{},
|
||||||
|
.tickables = .{},
|
||||||
|
.preTickables = .{},
|
||||||
|
.deltaTime = 0.0,
|
||||||
|
.lastEngineTime = 0.0,
|
||||||
|
.jobManager = try JobManager.create(allocator),
|
||||||
|
.eventors = .{},
|
||||||
|
.frameNumber = 0,
|
||||||
|
.exitListeners = .{},
|
||||||
|
.nfdRuntime = try nfd.NFDRuntime.create(allocator, .{}),
|
||||||
|
.delegates = EngineDelegates.init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
if (!self.dependentsDestroyed.load(.seq_cst)) {
|
||||||
|
self.destroyDependents();
|
||||||
|
}
|
||||||
|
core.engine_logs("shutting down job Manager");
|
||||||
|
self.jobManager.destroy();
|
||||||
|
|
||||||
|
if (self.destroyListCore.items.len > 0) {
|
||||||
|
var i: i32 = @intCast(self.destroyListCore.items.len - 1);
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
const item = self.destroyListCore.items[@as(usize, @intCast(i))];
|
||||||
|
if (item.vtable.deinit_func) |deinitFn| {
|
||||||
|
deinitFn(item.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.destroyListCore.deinit(self.allocator);
|
||||||
|
self.destroyListSimple.deinit(self.allocator);
|
||||||
|
|
||||||
|
self.renderers.deinit(self.allocator);
|
||||||
|
core.engine_logs("destroying engine objects");
|
||||||
|
self.engineObjects.deinit(self.allocator);
|
||||||
|
|
||||||
|
core.engine_logs("destroying eventors");
|
||||||
|
self.eventors.deinit(self.allocator);
|
||||||
|
|
||||||
|
core.engine_logs("destroying tickables");
|
||||||
|
self.tickables.deinit(self.allocator);
|
||||||
|
self.preTickables.deinit(self.allocator);
|
||||||
|
self.nfdRuntime.destroy();
|
||||||
|
self.delegates.deinit();
|
||||||
|
|
||||||
|
core.engine_logs("calling onexit listeners");
|
||||||
|
self.exitListeners.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates an engine object using the engine's allocator.
|
||||||
|
pub fn createObject(self: *@This(), comptime T: type, params: NeonObjectParams) !*T {
|
||||||
|
const newIndex = self.engineObjects.items.len;
|
||||||
|
const vtable = &@field(T, "NeonObjectTable");
|
||||||
|
const newObjectPtr = try vtable.init_func(self.allocator);
|
||||||
|
|
||||||
|
const newObjectRef = EngineObjectRef{
|
||||||
|
.ptr = @as(*anyopaque, @ptrCast(newObjectPtr)),
|
||||||
|
.vtable = vtable,
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.engineObjects.append(self.allocator, newObjectRef);
|
||||||
|
|
||||||
|
if (params.isCore) {
|
||||||
|
try self.destroyListCore.append(self.allocator, newObjectRef);
|
||||||
|
} else {
|
||||||
|
try self.destroyListSimple.append(self.allocator, newObjectRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.can_tick) {
|
||||||
|
if (!@hasDecl(T, "tick")) {
|
||||||
|
return error.RequestedTickNotAvailable; // tried to register a tickable for an object which does not implement tick
|
||||||
|
}
|
||||||
|
|
||||||
|
// register to tick table
|
||||||
|
try self.tickables.append(self.allocator, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(T, "engineDraw")) {
|
||||||
|
try self.renderers.append(self.allocator, newObjectRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(T, "preTick")) {
|
||||||
|
try self.preTickables.append(self.allocator, newObjectRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(T, "processEvents")) {
|
||||||
|
try self.eventors.append(self.allocator, newObjectRef); //
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(T, "onExitSignal")) {
|
||||||
|
try core.assert(@hasDecl(T, "readyToExit"));
|
||||||
|
try self.exitListeners.append(self.allocator, newObjectRef); //
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(T, "postInit")) {
|
||||||
|
try vtable.postInit_func.?(newObjectPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return @as(*T, @ptrCast(@alignCast(newObjectPtr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This()) !void {
|
||||||
|
tracy.FrameMark();
|
||||||
|
tracy.FrameMarkStart("frame");
|
||||||
|
defer tracy.FrameMarkEnd("frame");
|
||||||
|
|
||||||
|
const newTime = time.getEngineTime();
|
||||||
|
|
||||||
|
var z1 = tracy.ZoneN(@src(), "time updates");
|
||||||
|
|
||||||
|
if (self.first) {
|
||||||
|
self.first = false;
|
||||||
|
self.engineStartTime = newTime;
|
||||||
|
self.lastEngineTime = newTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTime < self.lastEngineTime) {
|
||||||
|
std.debug.print("Warning! negative deltaTime? clamping to 0.0 newTime: {d} lastEngineTime:{d}", .{
|
||||||
|
newTime,
|
||||||
|
self.lastEngineTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.deltaTime = @max(newTime - self.lastEngineTime, 0.0);
|
||||||
|
math.rollingAverage(&self.averageFrameTime, self.deltaTime, @floatFromInt(self.averageFrameSampleWindow));
|
||||||
|
z1.End();
|
||||||
|
|
||||||
|
var z2 = tracy.ZoneN(@src(), "debug updates");
|
||||||
|
for (self.delegates.onFrameDebugInfoEmitted.items) |l| {
|
||||||
|
try l.func(l.ctx, self.averageFrameTime);
|
||||||
|
}
|
||||||
|
z2.End();
|
||||||
|
|
||||||
|
self.frameNumber += 1;
|
||||||
|
|
||||||
|
var z3 = tracy.ZoneN(@src(), "platform event updates");
|
||||||
|
if (self.platformProcEventsFunc) |procEventsFn| {
|
||||||
|
try procEventsFn(self.platformCtx, self.frameNumber);
|
||||||
|
}
|
||||||
|
z3.End();
|
||||||
|
|
||||||
|
for (self.eventors.items) |*objectRef| {
|
||||||
|
objectRef.vtable.processEvents.?(objectRef.ptr, self.frameNumber) catch @panic("process event error");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nfdRuntime.processCallbacks();
|
||||||
|
|
||||||
|
var z4 = tracy.ZoneN(@src(), "pretick event updates");
|
||||||
|
for (self.preTickables.items) |*objectRef| {
|
||||||
|
objectRef.vtable.preTick_func.?(objectRef.ptr, self.deltaTime) catch @panic("pretick event error");
|
||||||
|
}
|
||||||
|
z4.End();
|
||||||
|
|
||||||
|
var z5 = tracy.ZoneN(@src(), "system object ticks");
|
||||||
|
var index: isize = @as(isize, @intCast(self.tickables.items.len)) - 1;
|
||||||
|
while (index >= 0) : (index -= 1) {
|
||||||
|
var z = tracy.Zone(@src());
|
||||||
|
const objectRef = self.engineObjects.items[self.tickables.items[@as(usize, @intCast(index))]];
|
||||||
|
objectRef.vtable.tick_func.?(objectRef.ptr, self.deltaTime);
|
||||||
|
z.Name(objectRef.vtable.typeName);
|
||||||
|
z.End();
|
||||||
|
}
|
||||||
|
z5.End();
|
||||||
|
|
||||||
|
const systemsThreadTime: f64 = time.getEngineTime() - newTime;
|
||||||
|
|
||||||
|
for (self.renderers.items) |*renderer| {
|
||||||
|
var z = tracy.Zone(@src());
|
||||||
|
z.Name(renderer.vtable.typeName);
|
||||||
|
|
||||||
|
renderer.vtable.engineDraw_func.?(renderer.ptr, self.deltaTime);
|
||||||
|
|
||||||
|
z.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
math.rollingAverage(&self.systemsThreadTime, systemsThreadTime, @floatFromInt(self.averageFrameSampleWindow));
|
||||||
|
self.lastEngineTime = newTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: *@This()) !void {
|
||||||
|
core.engine_logs("engine loop started");
|
||||||
|
const SystemsThread = struct {
|
||||||
|
engine: *Engine,
|
||||||
|
|
||||||
|
pub fn func(ctx: @This(), job: *core.JobContext) void {
|
||||||
|
_ = job;
|
||||||
|
|
||||||
|
tracy.SetThreadName("Systems Thread");
|
||||||
|
|
||||||
|
var exitSignaled: bool = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ctx.engine.tick() catch unreachable;
|
||||||
|
|
||||||
|
if (!exitSignaled and ctx.engine.exitSignal.load(.seq_cst)) {
|
||||||
|
exitSignaled = true;
|
||||||
|
core.engine_logs("Processing exit signals");
|
||||||
|
|
||||||
|
for (ctx.engine.exitListeners.items) |ref| {
|
||||||
|
ref.vtable.exitSignal_func.?(ref.ptr) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exitSignaled) {
|
||||||
|
var readyToExit: bool = true;
|
||||||
|
for (ctx.engine.exitListeners.items) |pending| {
|
||||||
|
if (!pending.vtable.readyToExit_func.?(pending.ptr)) {
|
||||||
|
readyToExit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readyToExit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.engine.exitConfirmed.store(true, .seq_cst);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try core.dispatchJob(SystemsThread{ .engine = self });
|
||||||
|
|
||||||
|
try self.mainLoop();
|
||||||
|
|
||||||
|
self.destroyDependents();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroyDependents(self: *@This()) void {
|
||||||
|
if (self.destroyListSimple.items.len > 0) {
|
||||||
|
var i: i32 = @intCast(self.destroyListSimple.items.len - 1);
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
const item = self.destroyListSimple.items[@as(usize, @intCast(i))];
|
||||||
|
if (item.vtable.deinit_func) |deinitFn| {
|
||||||
|
deinitFn(item.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.dependentsDestroyed.store(true, .seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mainLoop(self: *@This()) !void {
|
||||||
|
while (!self.exitConfirmed.load(.acquire)) {
|
||||||
|
if (self.platformPollFunc) |pollFunc| {
|
||||||
|
const z = core.tracy.ZoneN(@src(), "glfw input polling");
|
||||||
|
try pollFunc(self.platformCtx);
|
||||||
|
defer z.End();
|
||||||
|
}
|
||||||
|
self.jobManager.bump();
|
||||||
|
std.time.sleep(1000 * 1000); // 1ms delay between polling functions, effectively limits input to 1khz.
|
||||||
|
// (there is a noticable power consumption draw on laptops and battery based systems if this is unlimited)
|
||||||
|
try self.nfdRuntime.processMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(self: *@This()) void {
|
||||||
|
self.exitSignal.store(true, .release);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isShuttingDown(self: *@This()) bool {
|
||||||
|
return self.exitSignal.load(.monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exitFinished(self: *@This()) bool {
|
||||||
|
return self.exitConfirmed.load(.monotonic);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NeonObjectParams = struct {
|
||||||
|
can_tick: bool = false,
|
||||||
|
responds_to_events: bool = false,
|
||||||
|
isCore: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "comptime registration implementation" {}
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const logging = @import("logging.zig");
|
||||||
|
const input = @import("input.zig");
|
||||||
|
const p2 = @import("p2");
|
||||||
|
const engine_logs = logging.engine_logs;
|
||||||
|
|
||||||
|
const ObjectHandle = p2.ObjectHandle;
|
||||||
|
const Name = p2.Name;
|
||||||
|
const MakeName = p2.MakeName;
|
||||||
|
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
const AutoHashMap = std.AutoHashMap;
|
||||||
|
|
||||||
|
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
||||||
|
const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
|
||||||
|
|
||||||
|
pub fn InterfaceRef(comptime Vtable: type) type {
|
||||||
|
return struct {
|
||||||
|
ptr: *anyopaque,
|
||||||
|
vtable: *const Vtable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn MakeTypeName(comptime TargetType: type) Name {
|
||||||
|
const hashedName = comptime std.fmt.comptimePrint("{s}_{d}", .{ @typeName(TargetType), @sizeOf(TargetType) });
|
||||||
|
|
||||||
|
return MakeName(hashedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo.. engineObject is not the right word for this
|
||||||
|
|
||||||
|
pub const EngineDataEventError = error{
|
||||||
|
UnknownStatePanic,
|
||||||
|
BadInit,
|
||||||
|
UnknownError,
|
||||||
|
OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EngineObjectVTable = struct {
|
||||||
|
typeName: []const u8,
|
||||||
|
typeSize: usize,
|
||||||
|
typeAlign: usize,
|
||||||
|
|
||||||
|
init_func: *const fn (std.mem.Allocator) EngineDataEventError!*anyopaque,
|
||||||
|
tick_func: ?*const fn (*anyopaque, f64) void = null,
|
||||||
|
engineDraw_func: ?*const fn (*anyopaque, f64) void = null,
|
||||||
|
preTick_func: ?*const fn (*anyopaque, f64) EngineDataEventError!void = null,
|
||||||
|
deinit_func: ?*const fn (*anyopaque) void = null,
|
||||||
|
postInit_func: ?*const fn (*anyopaque) EngineDataEventError!void = null,
|
||||||
|
processEvents: ?*const fn (*anyopaque, u64) EngineDataEventError!void = null,
|
||||||
|
exitSignal_func: ?*const fn (*anyopaque) EngineDataEventError!void = null,
|
||||||
|
readyToExit_func: ?*const fn (*anyopaque) bool = null,
|
||||||
|
|
||||||
|
pub fn from(comptime TargetType: type) EngineObjectVTable {
|
||||||
|
const wrappedInit = struct {
|
||||||
|
const funcFind: @TypeOf(@field(TargetType, "init")) = @field(TargetType, "init");
|
||||||
|
|
||||||
|
pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque {
|
||||||
|
const newObject = funcFind(allocator) catch return error.BadInit;
|
||||||
|
return @as(*anyopaque, @ptrCast(newObject));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var self = EngineObjectVTable{
|
||||||
|
.typeName = @typeName(TargetType),
|
||||||
|
.typeSize = @sizeOf(TargetType),
|
||||||
|
.typeAlign = @alignOf(TargetType),
|
||||||
|
.init_func = wrappedInit.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "postInit")) {
|
||||||
|
const wrappedPostInit = struct {
|
||||||
|
pub fn func(pointer: *anyopaque) EngineDataEventError!void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
try ptr.postInit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.postInit_func = wrappedPostInit.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "preTick")) {
|
||||||
|
const Wrapped = struct {
|
||||||
|
pub fn func(pointer: *anyopaque, deltaTime: f64) EngineDataEventError!void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.preTick(deltaTime) catch return error.UnknownStatePanic;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.preTick_func = Wrapped.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "engineDraw")) {
|
||||||
|
const Wrapped = struct {
|
||||||
|
pub fn func(pointer: *anyopaque, deltaTime: f64) void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.engineDraw(deltaTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.engineDraw_func = Wrapped.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "tick")) {
|
||||||
|
const Wrapped = struct {
|
||||||
|
pub fn func(pointer: *anyopaque, deltaTime: f64) void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.tick(deltaTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tick_func = Wrapped.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "processEvents")) {
|
||||||
|
const wrappedProcessEvents = struct {
|
||||||
|
pub fn func(pointer: *anyopaque, frameNumber: u64) EngineDataEventError!void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
try ptr.processEvents(frameNumber);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.processEvents = wrappedProcessEvents.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "onExitSignal")) {
|
||||||
|
if (!@hasDecl(TargetType, "readyToExit")) {
|
||||||
|
@compileError("engine object implemented onExitSignal but did not implement fn readyToExit() bool");
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrappedProcessEvents = struct {
|
||||||
|
pub fn onExitSignal(pointer: *anyopaque) EngineDataEventError!void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
try ptr.onExitSignal();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readyToExit(pointer: *anyopaque) bool {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
return ptr.readyToExit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.exitSignal_func = wrappedProcessEvents.onExitSignal;
|
||||||
|
self.readyToExit_func = wrappedProcessEvents.readyToExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(TargetType, "deinit")) {
|
||||||
|
const wrappedDeinit = struct {
|
||||||
|
pub fn func(pointer: *anyopaque) void {
|
||||||
|
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.deinit_func = wrappedDeinit.func;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EngineObjectRef = InterfaceRef(EngineObjectVTable);
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// todo need a better timing system than this
|
||||||
|
pub fn getEngineTime() f64 {
|
||||||
|
return @as(f64, @floatFromInt(std.time.milliTimestamp())) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the current system timestamp in nanoseconds
|
||||||
|
pub fn getEngineTimeStamp() i128 {
|
||||||
|
return std.time.nanoTimestamp();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const nfd = @import("nfd");
|
||||||
|
|
||||||
|
pub const AsyncOpenFileDialogArgs = nfd.AsyncOpenFileDialogArgs;
|
||||||
|
|
||||||
|
pub fn openFileDialog(defaultPath: []const u8, filters: []const u8) ![*c]const u8 {
|
||||||
|
return try core.getEngine().nfdRuntime.openFileDialog(defaultPath, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn openFolderDialog(defaultPath: []const u8, context: ?*anyopaque) ![*c]const u8 {
|
||||||
|
return try core.getEngine().nfdRuntime.openFolderDialog(defaultPath, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asyncOpenFile(args: AsyncOpenFileDialogArgs) !void {
|
||||||
|
return try core.getEngine().nfdRuntime.asyncOpenFileDialog(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asyncOpenFolder(args: AsyncOpenFileDialogArgs) !void {
|
||||||
|
return try core.getEngine().nfdRuntime.asyncOpenFolderDialog(args);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const logging = @import("logging.zig");
|
||||||
|
|
||||||
|
pub fn writeToFile(data: []const u8, path: []const u8) !void {
|
||||||
|
const file = try std.fs.cwd().createFile(
|
||||||
|
path,
|
||||||
|
.{
|
||||||
|
.read = true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const bytes_written = try file.writeAll(data);
|
||||||
|
_ = bytes_written;
|
||||||
|
logging.engine_log("written: bytes to {s}", .{path});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn splitIntoLines(file_contents: []const u8) std.mem.SplitIterator(u8) {
|
||||||
|
// find a \n and see if it has \r\n
|
||||||
|
var index: u32 = 0;
|
||||||
|
while (index < file_contents.len) : (index += 1) {
|
||||||
|
if (file_contents[index] == '\n') {
|
||||||
|
if (index > 0) {
|
||||||
|
if (file_contents[index - 1] == '\r') {
|
||||||
|
return std.mem.split(u8, file_contents, "\r\n");
|
||||||
|
} else {
|
||||||
|
return std.mem.split(u8, file_contents, "\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return std.mem.split(u8, file_contents, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std.mem.split(u8, file_contents, "\n");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// procedural functions to describe input primitives with which
|
||||||
|
// we can build on the rest of the engine.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const engineObject = @import("engineObject.zig");
|
||||||
|
|
||||||
|
pub const InputSubsystem = struct {
|
||||||
|
const Self = @This();
|
||||||
|
pub var NeonObjectTable: engineObject.EngineObjectVTable = engineObject.EngineObjectVTable.from(Self);
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
const self = Self{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,619 @@
|
||||||
|
pub const BindingType = enum(u8) {
|
||||||
|
action,
|
||||||
|
axis1d,
|
||||||
|
axis2d,
|
||||||
|
};
|
||||||
|
|
||||||
|
const glfw_release = 0;
|
||||||
|
|
||||||
|
pub const IOEvent = union(enum(u8)) {
|
||||||
|
windowFocused: struct { focused: c_int },
|
||||||
|
mousePosition: struct { x: f64, y: f64 },
|
||||||
|
mouseButton: struct { button: c_int, action: c_int, mods: c_int },
|
||||||
|
scroll: struct { xoffset: f64, yoffset: f64 },
|
||||||
|
key: struct { key: c_int, scancode: c_int, action: c_int, mods: c_int },
|
||||||
|
windowResize: struct { newSize: core.Vector2f },
|
||||||
|
codepoint: c_uint,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ActionEvent = enum(u8) {
|
||||||
|
keyUp,
|
||||||
|
keyDown,
|
||||||
|
keyHeld,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ActionBindingKey = struct {
|
||||||
|
key: Key,
|
||||||
|
event: ActionEvent = .keyDown,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn arenaAlloc() std.mem.Allocator {
|
||||||
|
return gInputStack.arena.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn BindingData(Listener: type, Func: type) type {
|
||||||
|
return struct {
|
||||||
|
keysDown: KeysDownQueue,
|
||||||
|
keysUp: std.ArrayListUnmanaged(Key) = .{},
|
||||||
|
listeners: std.ArrayListUnmanaged(Listener) = .{},
|
||||||
|
consumed: bool = false,
|
||||||
|
routed: bool = false,
|
||||||
|
count: u32 = 0,
|
||||||
|
name: core.Name,
|
||||||
|
|
||||||
|
pub const KeysDownQueue = core.RingQueueU(struct { key: Key, first: bool = true });
|
||||||
|
|
||||||
|
pub fn init(name: core.Name) @This() {
|
||||||
|
return .{
|
||||||
|
.name = name,
|
||||||
|
.keysDown = KeysDownQueue.init(arenaAlloc(), 256) catch unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addListener(self: *@This(), ctx: ?*anyopaque, func: Func) u32 {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.listeners.append(allocator, .{ .id = self.count, .ctx = ctx, .func = func }) catch unreachable;
|
||||||
|
self.count +%= 1;
|
||||||
|
|
||||||
|
return self.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeListener(self: *@This(), id: u32) void {
|
||||||
|
for (self.listeners.items.len, 0..) |listener, i| {
|
||||||
|
if (listener.id == id) {
|
||||||
|
self.listeners.swapRemove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setKeyDown(self: *@This(), key: Key) void {
|
||||||
|
self.keysDown.push(.{ .key = key, .first = true }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setKeyHeld(self: *@This(), key: Key) void {
|
||||||
|
// core.engine_log("repeating key {any}", .{key});
|
||||||
|
self.keysDown.push(.{ .key = key, .first = false }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setKeyUp(self: *@This(), key: Key) void {
|
||||||
|
self.keysUp.append(arenaAlloc(), key) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.keysDown.deinit(arenaAlloc());
|
||||||
|
self.listeners.deinit(arenaAlloc());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ActionBinding = struct {
|
||||||
|
data: BindingData(Listener, ActionFunc),
|
||||||
|
keys: std.ArrayListUnmanaged(ActionBindingKey) = .{},
|
||||||
|
|
||||||
|
pub const Listener = struct {
|
||||||
|
id: u32,
|
||||||
|
ctx: ?*anyopaque,
|
||||||
|
func: ActionFunc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(name: core.Name) !*@This() {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.data = BindingData(Listener, ActionFunc).init(name),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
// pushes this binding to the active layer
|
||||||
|
pub fn activate(self: *@This()) void {
|
||||||
|
gInputStack.active.addBindingByName(self.data.name, self) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(self: *@This()) void {
|
||||||
|
gInputStack.active.removeBindingByName(self.data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addKey(self: *@This(), key: Key, event: ActionEvent) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.keys.append(allocator, .{ .key = key, .event = event }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.data.deinit();
|
||||||
|
self.keys.deinit(allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ActionFunc = *const fn (?*anyopaque, ActionEvent) void;
|
||||||
|
|
||||||
|
pub const AxisBindingKeyMagnitude = struct {
|
||||||
|
key: Key,
|
||||||
|
magnitude: f32 = 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Axis1dFunc = *const fn (?*anyopaque, f32) void;
|
||||||
|
|
||||||
|
pub const Axis1dBinding = struct {
|
||||||
|
data: BindingData(Listener, Axis1dFunc),
|
||||||
|
keys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
|
||||||
|
magnitude: f32 = 0.0,
|
||||||
|
|
||||||
|
pub const Listener = struct {
|
||||||
|
id: u32,
|
||||||
|
ctx: ?*anyopaque,
|
||||||
|
func: Axis1dFunc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(name: core.Name) !*@This() {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.data = BindingData(Listener, Axis1dFunc).init(name),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes this binding to the active layer
|
||||||
|
pub fn activate(self: *@This()) void {
|
||||||
|
gInputStack.active.addBindingByName(self.data.name, self) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(self: *@This()) void {
|
||||||
|
gInputStack.active.removeBindingByName(self.data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addKey(self: *@This(), key: Key, magnitude: f32) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.keys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.data.deinit();
|
||||||
|
self.keys.deinit(allocator);
|
||||||
|
self.listeners.deinit(allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Axis2dFunc = *const fn (?*anyopaque, core.Vector2f) void;
|
||||||
|
pub const Axis2dBinding = struct {
|
||||||
|
data: BindingData(Listener, Axis2dFunc),
|
||||||
|
yKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
|
||||||
|
xKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
|
||||||
|
|
||||||
|
magnitude: core.Vector2f = core.Vector2f.Zeroes,
|
||||||
|
|
||||||
|
pub const Listener = struct {
|
||||||
|
id: u32,
|
||||||
|
ctx: ?*anyopaque,
|
||||||
|
func: Axis2dFunc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(name: core.Name) !*@This() {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.data = BindingData(Listener, Axis2dFunc).init(name),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushes this binding to the active layer
|
||||||
|
pub fn activate(self: *@This()) void {
|
||||||
|
gInputStack.active.addBindingByName(self.data.name, self) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(self: *@This()) void {
|
||||||
|
gInputStack.active.removeBindingByName(self.data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addKey(self: *@This(), key: Key, magnitude: f32, axis: enum { x, y }) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
switch (axis) {
|
||||||
|
.x => {
|
||||||
|
self.xKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
|
||||||
|
},
|
||||||
|
.y => {
|
||||||
|
self.yKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
const allocator = arenaAlloc();
|
||||||
|
self.xKeys.deinit(allocator);
|
||||||
|
self.yKeys.deinit(allocator);
|
||||||
|
self.data.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Binding = union(BindingType) {
|
||||||
|
action: *ActionBinding,
|
||||||
|
axis1d: *Axis1dBinding,
|
||||||
|
axis2d: *Axis2dBinding,
|
||||||
|
|
||||||
|
pub fn setKeyDown(self: @This(), key: Key) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
p.data.setKeyDown(key);
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
p.data.setKeyDown(key);
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
p.data.setKeyDown(key);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setKeyHeld(self: @This(), key: Key) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
p.data.setKeyHeld(key);
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
p.data.setKeyHeld(key);
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
p.data.setKeyHeld(key);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setKeyUp(self: @This(), key: Key) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
p.data.setKeyUp(key);
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
p.data.setKeyUp(key);
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
p.data.setKeyUp(key);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendAxisUpdates(self: @This()) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => {},
|
||||||
|
.axis1d => |p| {
|
||||||
|
p.data.routed = false;
|
||||||
|
for (p.data.listeners.items) |listener| {
|
||||||
|
listener.func(listener.ctx, p.magnitude);
|
||||||
|
}
|
||||||
|
p.magnitude = 0.0;
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
p.data.routed = false;
|
||||||
|
for (p.data.listeners.items) |listener| {
|
||||||
|
listener.func(listener.ctx, p.magnitude);
|
||||||
|
}
|
||||||
|
p.magnitude = core.Vector2f.Zeroes;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routeBindingEvent(self: @This(), key: Key, event: ActionEvent, consumedOut: *bool) bool {
|
||||||
|
var consumed: bool = false;
|
||||||
|
var routed: bool = false;
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
for (p.keys.items) |keyBinding| {
|
||||||
|
if (keyBinding.key == key and event == keyBinding.event) {
|
||||||
|
consumed = p.data.consumed;
|
||||||
|
routed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routed) {
|
||||||
|
for (p.data.listeners.items) |listener| {
|
||||||
|
listener.func(listener.ctx, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.data.routed = routed;
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
for (p.keys.items) |keyBinding| {
|
||||||
|
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
|
||||||
|
p.magnitude += keyBinding.magnitude;
|
||||||
|
consumed = p.data.consumed;
|
||||||
|
routed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.magnitude = std.math.clamp(p.magnitude, -1.0, 1.0);
|
||||||
|
|
||||||
|
p.data.routed = routed;
|
||||||
|
// for (p.data.listeners.items) |listener| {
|
||||||
|
// listener.func(listener.ctx, magnitude);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
for (p.xKeys.items) |keyBinding| {
|
||||||
|
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
|
||||||
|
p.magnitude.x += keyBinding.magnitude;
|
||||||
|
consumed = p.data.consumed;
|
||||||
|
routed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p.yKeys.items) |keyBinding| {
|
||||||
|
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
|
||||||
|
p.magnitude.y += keyBinding.magnitude;
|
||||||
|
consumed = p.data.consumed;
|
||||||
|
routed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.magnitude.x = std.math.clamp(p.magnitude.x, -1.0, 1.0);
|
||||||
|
p.magnitude.y = std.math.clamp(p.magnitude.y, -1.0, 1.0);
|
||||||
|
|
||||||
|
// for (p.data.listeners.items) |listener| {
|
||||||
|
// listener.func(listener.ctx, p.magnitude);
|
||||||
|
// }
|
||||||
|
p.data.routed = routed;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
consumedOut.* = consumed;
|
||||||
|
|
||||||
|
return routed;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn processKeysHeldDown(self: @This(), p: anytype) void {
|
||||||
|
const keysCount = p.data.keysDown.count();
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < keysCount) : (i += 1) {
|
||||||
|
const keydown = p.data.keysDown.pop().?;
|
||||||
|
var isKeyUp: bool = false;
|
||||||
|
|
||||||
|
for (p.data.keysUp.items) |up| {
|
||||||
|
if (up == keydown.key) {
|
||||||
|
isKeyUp = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var consumed: bool = false;
|
||||||
|
|
||||||
|
if (!isKeyUp and !keydown.first) {
|
||||||
|
_ = self.routeBindingEvent(keydown.key, .keyHeld, &consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKeyUp) {
|
||||||
|
p.data.setKeyHeld(keydown.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.data.keysUp.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn processHeldKeys(self: @This()) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
self.processKeysHeldDown(p);
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
self.processKeysHeldDown(p);
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
self.processKeysHeldDown(p);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn releaseKeys(self: @This()) void {
|
||||||
|
switch (self) {
|
||||||
|
.action => |p| {
|
||||||
|
p.keysDown.clearRetainingCapacity();
|
||||||
|
},
|
||||||
|
.axis1d => |p| {
|
||||||
|
p.keysDown.clearRetainingCapacity();
|
||||||
|
},
|
||||||
|
.axis2d => |p| {
|
||||||
|
p.keysDown.clearRetainingCapacity();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BindingLayer = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
bindingsByName: std.AutoHashMapUnmanaged(u32, u32) = .{},
|
||||||
|
bindingStack: std.ArrayListUnmanaged(Binding) = .{},
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addBindingByName(self: *@This(), _name: core.Name, new: anytype) !void {
|
||||||
|
var name = _name;
|
||||||
|
if (self.bindingsByName.contains(name.handle())) {
|
||||||
|
self.removeBindingByName(name);
|
||||||
|
}
|
||||||
|
try self.bindingsByName.put(self.allocator, name.handle(), @intCast(self.bindingStack.items.len));
|
||||||
|
|
||||||
|
var newBinding: Binding = undefined;
|
||||||
|
|
||||||
|
switch (@TypeOf(new)) {
|
||||||
|
*ActionBinding => {
|
||||||
|
newBinding = .{ .action = new };
|
||||||
|
},
|
||||||
|
*Axis1dBinding => {
|
||||||
|
newBinding = .{ .axis1d = new };
|
||||||
|
},
|
||||||
|
*Axis2dBinding => {
|
||||||
|
newBinding = .{ .axis2d = new };
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@panic("uh oh");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.bindingStack.append(self.allocator, newBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeBindingByName(self: *@This(), _name: core.Name) void {
|
||||||
|
var name = _name;
|
||||||
|
_ = self.bindingStack.orderedRemove(self.bindingsByName.get(name.handle()).?);
|
||||||
|
_ = self.bindingsByName.remove(name.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.bindingsByName.deinit(self.allocator);
|
||||||
|
self.bindingStack.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// not gonna actually deal with layers right now
|
||||||
|
pub const InputStack = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
active: *BindingLayer,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
keysDown: std.AutoHashMapUnmanaged(Key, bool) = .{},
|
||||||
|
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
|
.active = try BindingLayer.create(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
gInputStack = self;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updatePreviousInputs(self: *@This()) void {
|
||||||
|
if (self.active.bindingStack.items.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: i32 = @intCast(self.active.bindingStack.items.len - 1);
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
const binding = &self.active.bindingStack.items[@intCast(i)];
|
||||||
|
binding.processHeldKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendAxisUpdates(self: *@This()) void {
|
||||||
|
for (self.active.bindingStack.items) |binding| {
|
||||||
|
binding.sendAxisUpdates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn routeKeyEvent(self: *@This(), key: Key, event: ActionEvent) void {
|
||||||
|
// for all bindings in reverse order from this layer, route the input, stop the
|
||||||
|
// route if the input was consumed
|
||||||
|
if (self.active.bindingStack.items.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: i32 = @intCast(self.active.bindingStack.items.len - 1);
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
const binding = self.active.bindingStack.items[@intCast(i)];
|
||||||
|
var consumed: bool = false;
|
||||||
|
var routed: bool = false;
|
||||||
|
|
||||||
|
if (binding.routeBindingEvent(key, event, &consumed)) {
|
||||||
|
routed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event == .keyDown) {
|
||||||
|
binding.setKeyDown(key);
|
||||||
|
}
|
||||||
|
if (event == .keyHeld) {
|
||||||
|
binding.setKeyHeld(key);
|
||||||
|
}
|
||||||
|
if (event == .keyUp) {
|
||||||
|
binding.setKeyUp(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routeEvent(self: *@This(), eventToRoute: IOEvent) void {
|
||||||
|
switch (eventToRoute) {
|
||||||
|
.key => |key| {
|
||||||
|
const keyEvent: ActionEvent = @enumFromInt(@as(u8, @intCast(key.action)));
|
||||||
|
if (keyEvent != .keyHeld) {
|
||||||
|
self.routeKeyEvent(@enumFromInt(key.key), keyEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.mouseButton => |button| {
|
||||||
|
const keyEvent: ActionEvent = @enumFromInt(@as(u8, @intCast(button.action)));
|
||||||
|
if (keyEvent != .keyHeld) {
|
||||||
|
self.routeKeyEvent(@enumFromInt(button.button + 10001), keyEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.active.destroy();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var gInputStack: *InputStack = undefined;
|
||||||
|
|
||||||
|
pub fn getInputStack() *InputStack {
|
||||||
|
return gInputStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initInputStack() !void {
|
||||||
|
gInputStack = try core.createObject(InputStack, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a binding for a lua type
|
||||||
|
// TODO
|
||||||
|
// pub fn LuaBinding(comptime T: type, comptime typeName: []const u8) type {
|
||||||
|
// return struct {
|
||||||
|
// p: ?*T,
|
||||||
|
//
|
||||||
|
// pub const PodDataTable = pod.DataTable{
|
||||||
|
// .name = typeName,
|
||||||
|
// .funcs = &.{},
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// pub fn create() @This() {
|
||||||
|
// return .{};
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
const lua = core.lua;
|
||||||
|
const pod = lua.pod;
|
||||||
|
|
||||||
|
const Key = @import("keys.zig").Key;
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("../core.zig");
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
pub const Key = enum(i32) {
|
||||||
|
Unknown = -1,
|
||||||
|
Space = 32,
|
||||||
|
Apostrophe = 39,
|
||||||
|
Comma = 44,
|
||||||
|
Period = 45,
|
||||||
|
Slash = 47,
|
||||||
|
@"0" = 48,
|
||||||
|
@"1" = 49,
|
||||||
|
@"2" = 50,
|
||||||
|
@"3" = 51,
|
||||||
|
@"4" = 52,
|
||||||
|
@"5" = 53,
|
||||||
|
@"6" = 54,
|
||||||
|
@"7" = 55,
|
||||||
|
@"8" = 56,
|
||||||
|
@"9" = 57,
|
||||||
|
Semicolon = 59,
|
||||||
|
Equal = 61,
|
||||||
|
A = 65,
|
||||||
|
B = 66,
|
||||||
|
C = 67,
|
||||||
|
D = 68,
|
||||||
|
E = 69,
|
||||||
|
F = 70,
|
||||||
|
G = 71,
|
||||||
|
H = 72,
|
||||||
|
I = 73,
|
||||||
|
J = 74,
|
||||||
|
K = 75,
|
||||||
|
L = 76,
|
||||||
|
M = 77,
|
||||||
|
N = 78,
|
||||||
|
O = 79,
|
||||||
|
P = 80,
|
||||||
|
Q = 81,
|
||||||
|
R = 82,
|
||||||
|
S = 83,
|
||||||
|
T = 84,
|
||||||
|
U = 85,
|
||||||
|
V = 86,
|
||||||
|
W = 87,
|
||||||
|
X = 88,
|
||||||
|
Y = 89,
|
||||||
|
Z = 90,
|
||||||
|
LeftBrackSq = 91,
|
||||||
|
BackSlash = 92,
|
||||||
|
RightBrackSq = 93,
|
||||||
|
Grave = 96,
|
||||||
|
World1 = 161,
|
||||||
|
World2 = 162,
|
||||||
|
Escape = 256,
|
||||||
|
Enter = 257,
|
||||||
|
Tab = 258,
|
||||||
|
Backspace = 259,
|
||||||
|
Insert = 260,
|
||||||
|
Delete = 261,
|
||||||
|
Right = 262,
|
||||||
|
Left = 263,
|
||||||
|
Down = 264,
|
||||||
|
Up = 265,
|
||||||
|
PageUp = 266,
|
||||||
|
PageDown = 267,
|
||||||
|
Home = 268,
|
||||||
|
End = 269,
|
||||||
|
CapsLock = 280,
|
||||||
|
ScrollLock = 281,
|
||||||
|
NumLock = 282,
|
||||||
|
PrintScreen = 283,
|
||||||
|
Pause = 284,
|
||||||
|
F1 = 290,
|
||||||
|
F2 = 291,
|
||||||
|
F3 = 292,
|
||||||
|
F4 = 293,
|
||||||
|
F5 = 294,
|
||||||
|
F6 = 295,
|
||||||
|
F7 = 296,
|
||||||
|
F8 = 297,
|
||||||
|
F9 = 298,
|
||||||
|
F10 = 299,
|
||||||
|
F11 = 300,
|
||||||
|
F12 = 301,
|
||||||
|
F13 = 302,
|
||||||
|
F14 = 303,
|
||||||
|
F15 = 304,
|
||||||
|
F16 = 305,
|
||||||
|
F17 = 306,
|
||||||
|
F18 = 307,
|
||||||
|
F19 = 308,
|
||||||
|
F20 = 309,
|
||||||
|
F21 = 310,
|
||||||
|
F22 = 311,
|
||||||
|
F23 = 312,
|
||||||
|
F24 = 313,
|
||||||
|
F25 = 314,
|
||||||
|
Kp0 = 320,
|
||||||
|
Kp1 = 321,
|
||||||
|
Kp2 = 322,
|
||||||
|
Kp3 = 323,
|
||||||
|
Kp4 = 324,
|
||||||
|
Kp5 = 325,
|
||||||
|
Kp6 = 326,
|
||||||
|
Kp7 = 327,
|
||||||
|
Kp8 = 328,
|
||||||
|
Kp9 = 329,
|
||||||
|
KpDecimal = 330,
|
||||||
|
KpDivide = 331,
|
||||||
|
KpMultiply = 332,
|
||||||
|
KpSubtract = 333,
|
||||||
|
KpAdd = 334,
|
||||||
|
KpEnter = 335,
|
||||||
|
KpEqual = 336,
|
||||||
|
LeftShift = 340,
|
||||||
|
LeftControl = 341,
|
||||||
|
LeftAlt = 342,
|
||||||
|
LeftSuper = 343,
|
||||||
|
RightShift = 344,
|
||||||
|
RightControl = 345,
|
||||||
|
RightAlt = 346,
|
||||||
|
RightSuper = 347,
|
||||||
|
Menu = 348,
|
||||||
|
Mouse1 = 10001,
|
||||||
|
Mouse2 = 10002,
|
||||||
|
Mouse3 = 10003,
|
||||||
|
Mouse4 = 10004,
|
||||||
|
Mouse5 = 10005,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
moveAxis = CreateInput2DAxis()
|
||||||
|
moveAxis:addKey(Keys.W, 1.0, Axis.Y)
|
||||||
|
moveAxis:addKey(Keys.S, -1.0, Axis.Y)
|
||||||
|
|
||||||
|
moveAxis:addKey(Keys.A, -1.0, Axis.X)
|
||||||
|
moveAxis:addKey(Keys.D, 1.0, Axis.X)
|
||||||
|
moveAxis:addListener(balls, )
|
||||||
|
|
||||||
|
Input.addBinding("move", moveAxis, true)
|
||||||
|
|
||||||
|
Input.removeBinding("move")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Atomic = std.atomic.Value;
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const tracy = core.tracy;
|
||||||
|
const RingQueueU = core.RingQueueU;
|
||||||
|
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
||||||
|
|
||||||
|
const mutex_job_queue = core.BuildOption("mutex_job_queue");
|
||||||
|
|
||||||
|
pub const JobManager = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
// need a mutex for the jobQueue... todo later
|
||||||
|
jobQueue: RingQueueU(JobContext),
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
jobQueueConcurrent: core.ConcurrentQueueU(JobContext),
|
||||||
|
workers: []*JobWorker,
|
||||||
|
numCpus: usize,
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
var self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = JobManager{
|
||||||
|
.allocator = allocator,
|
||||||
|
.numCpus = std.Thread.getCpuCount() catch 4,
|
||||||
|
.jobQueue = RingQueueU(JobContext).init(allocator, 4096) catch unreachable,
|
||||||
|
.jobQueueConcurrent = core.ConcurrentQueueU(JobContext).initCapacity(allocator, 4096) catch unreachable,
|
||||||
|
.workers = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.workers = self.allocator.alloc(*JobWorker, @max(self.numCpus - 2, 4)) catch unreachable;
|
||||||
|
core.engine_log("allocating worker count : {d}", .{self.workers.len});
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < self.workers.len) : (i += 1) {
|
||||||
|
self.workers[i] = JobWorker.init(self.allocator, i) catch unreachable;
|
||||||
|
self.workers[i].workerId = i;
|
||||||
|
self.workers[i].manager = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newJob(self: *@This(), capture: anytype) !void {
|
||||||
|
const Lambda = @TypeOf(capture);
|
||||||
|
const ctx = try JobContext.new(self.allocator, Lambda, capture);
|
||||||
|
|
||||||
|
if (mutex_job_queue) {
|
||||||
|
self.mutex.lock();
|
||||||
|
try self.jobQueue.push(ctx);
|
||||||
|
self.mutex.unlock();
|
||||||
|
} else {
|
||||||
|
try self.jobQueueConcurrent.push(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bump(self: *@This()) void {
|
||||||
|
var shouldBump: bool = false;
|
||||||
|
|
||||||
|
if (mutex_job_queue) {
|
||||||
|
shouldBump = self.jobQueue.count() > 0;
|
||||||
|
} else {
|
||||||
|
shouldBump = self.jobQueueConcurrent.count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldBump) {
|
||||||
|
for (self.workers) |worker| {
|
||||||
|
if (!worker.isBusy()) {
|
||||||
|
worker.wake();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
for (self.workers) |worker| {
|
||||||
|
worker.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.workers);
|
||||||
|
|
||||||
|
self.clearJobs();
|
||||||
|
|
||||||
|
self.jobQueue.deinit(self.allocator);
|
||||||
|
self.jobQueueConcurrent.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearJobs(self: *@This()) void {
|
||||||
|
var jobCtx: ?JobContext = null;
|
||||||
|
|
||||||
|
if (mutex_job_queue) {
|
||||||
|
self.mutex.lock();
|
||||||
|
jobCtx = self.jobQueue.pop();
|
||||||
|
self.mutex.unlock();
|
||||||
|
} else {
|
||||||
|
jobCtx = self.jobQueueConcurrent.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (jobCtx) |*c| {
|
||||||
|
c.deinit();
|
||||||
|
|
||||||
|
if (mutex_job_queue) {
|
||||||
|
self.mutex.lock();
|
||||||
|
jobCtx = self.jobQueue.pop();
|
||||||
|
self.mutex.unlock();
|
||||||
|
} else {
|
||||||
|
jobCtx = self.jobQueueConcurrent.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const JobWorker = struct {
|
||||||
|
detached: bool = true, // most threads are detached, completions are handled via callbacks.
|
||||||
|
currentJobContext: ?JobContext = null,
|
||||||
|
workerThread: std.Thread,
|
||||||
|
futex: Atomic(u32) = Atomic(u32).init(0),
|
||||||
|
current: u32 = 0,
|
||||||
|
shouldDie: Atomic(bool) = Atomic(bool).init(false),
|
||||||
|
busy: Atomic(bool) = Atomic(bool).init(false),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
workerId: usize = 0,
|
||||||
|
manager: ?*JobManager = null,
|
||||||
|
|
||||||
|
pub fn wake(self: *JobWorker) void {
|
||||||
|
// this needs to be re-evaluated, it's nice for system performance but
|
||||||
|
// a 1-2ms worst case wake time is kind of unacceptable.
|
||||||
|
std.Thread.Futex.wake(&self.futex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, workerNumber: usize) !*@This() {
|
||||||
|
const self = try allocator.create(JobWorker);
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.workerThread = try std.Thread.spawn(.{}, @This().workerThreadFunc, .{self}),
|
||||||
|
.workerId = workerNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isBusy(self: *@This()) bool {
|
||||||
|
return self.busy.load(.acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn workerThreadFunc(self: *@This()) void {
|
||||||
|
const printed = std.fmt.allocPrintZ(self.allocator, "WorkerThread_{d}", .{self.workerId}) catch unreachable;
|
||||||
|
tracy.InitThread();
|
||||||
|
tracy.SetThreadName(@as([*:0]u8, @ptrCast(printed.ptr)));
|
||||||
|
|
||||||
|
self.allocator.free(printed);
|
||||||
|
|
||||||
|
var wakeGrabCount: u32 = 1;
|
||||||
|
|
||||||
|
while (!self.shouldDie.load(.acquire)) {
|
||||||
|
if (self.currentJobContext != null) {
|
||||||
|
wakeGrabCount = 0;
|
||||||
|
self.busy.store(true, .seq_cst);
|
||||||
|
var ctx = self.currentJobContext.?;
|
||||||
|
ctx.func(ctx.capture, &ctx);
|
||||||
|
self.busy.store(false, .seq_cst);
|
||||||
|
ctx.deinit();
|
||||||
|
self.currentJobContext = null;
|
||||||
|
} else {
|
||||||
|
std.Thread.Futex.wait(&self.futex, self.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.manager) |manager| {
|
||||||
|
if (mutex_job_queue) {
|
||||||
|
self.manager.?.mutex.lock();
|
||||||
|
if (self.manager.?.jobQueue.count() > 0) {
|
||||||
|
self.currentJobContext = self.manager.?.jobQueue.pop().?;
|
||||||
|
}
|
||||||
|
self.manager.?.mutex.unlock();
|
||||||
|
} else {
|
||||||
|
self.currentJobContext = manager.jobQueueConcurrent.pop();
|
||||||
|
//while (wakeGrabCount > 0) : (wakeGrabCount -= 1) {
|
||||||
|
// self.currentJobContext = manager.jobQueueConcurrent.pop();
|
||||||
|
// if (self.currentJobContext != null) {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// std.time.sleep(1000 * 1000);
|
||||||
|
//}
|
||||||
|
wakeGrabCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.currentJobContext) |*ctx| {
|
||||||
|
ctx.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.shouldDie.store(true, .seq_cst);
|
||||||
|
self.wake();
|
||||||
|
self.workerThread.join();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const JobContext = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator, //todo, backed arena allocator would be sick for this.
|
||||||
|
func: *const fn (*anyopaque, *JobContext) void, // todo, add an error for job funcs
|
||||||
|
capture: *anyopaque = undefined,
|
||||||
|
destroyFunc: *const fn (*anyopaque, std.mem.Allocator) void,
|
||||||
|
|
||||||
|
const align8_struct = struct { size: u64 };
|
||||||
|
|
||||||
|
pub fn new(allocator: std.mem.Allocator, comptime CaptureType: type, capture: CaptureType) !JobContext {
|
||||||
|
if (!@hasDecl(CaptureType, "func")) {
|
||||||
|
return error.NoValidLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrap = struct {
|
||||||
|
pub fn wrappedFunc(pointer: *anyopaque, context: *JobContext) void {
|
||||||
|
var ptr = @as(*CaptureType, @ptrCast(@alignCast(pointer)));
|
||||||
|
ptr.func(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrappedDestroy(ptr: *anyopaque, alloc: std.mem.Allocator) void {
|
||||||
|
const p = @as(*CaptureType, @ptrCast(@alignCast(ptr)));
|
||||||
|
alloc.destroy(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const self = Self{
|
||||||
|
.allocator = allocator,
|
||||||
|
.func = Wrap.wrappedFunc,
|
||||||
|
.destroyFunc = Wrap.wrappedDestroy,
|
||||||
|
.capture = try allocator.create(CaptureType),
|
||||||
|
};
|
||||||
|
const ptr = @as(*CaptureType, @ptrCast(@alignCast(self.capture)));
|
||||||
|
ptr.* = capture;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.destroyFunc(self.capture, self.allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const tracy = @import("tracy");
|
||||||
|
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const slow_logging = core.BuildOption("slow_logging");
|
||||||
|
const zero_logging = core.BuildOption("zero_logging");
|
||||||
|
|
||||||
|
const LogBufferSize = 1 * 1024 * 1024; // 1Mb buffer log
|
||||||
|
|
||||||
|
var gLoggerSys: ?*LoggerSys = null;
|
||||||
|
|
||||||
|
pub fn printRaw(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
if (zero_logging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slow_logging) {
|
||||||
|
std.debug.print(fmt, args);
|
||||||
|
} else {
|
||||||
|
if (gLoggerSys) |loggerSys| {
|
||||||
|
loggerSys.print(fmt, args) catch std.debug.print("!> " ++ fmt, args);
|
||||||
|
} else {
|
||||||
|
std.debug.print(fmt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printInner(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
if (zero_logging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slow_logging) {
|
||||||
|
std.debug.print("> " ++ fmt, args);
|
||||||
|
} else {
|
||||||
|
if (gLoggerSys) |loggerSys| {
|
||||||
|
loggerSys.print(fmt, args) catch std.debug.print("!> " ++ fmt, args);
|
||||||
|
} else {
|
||||||
|
std.debug.print("> " ++ fmt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[GAME ]: " ++ fmt ++ "\n", args);
|
||||||
|
printInner("[SCRIPT ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_logs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[GAME ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[UI ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_logs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[UI ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn engine_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[ENGINE ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn engine_logs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[ENGINE ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn engine_err(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[ENGINE ]: ERROR!! " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn engine_errs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[ENGINE ]: ERROR!! " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[TEST ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_logs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[TEST ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_log(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
printInner("[GRAPHICS ]: " ++ fmt ++ "\n", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_logs(comptime fmt: []const u8) void {
|
||||||
|
printInner("[GRAPHICS ]: " ++ fmt ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FileLog = struct {
|
||||||
|
buffer: std.ArrayList(u8),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !@This() {
|
||||||
|
const self = @This(){
|
||||||
|
.buffer = std.ArrayList(u8).init(allocator),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *@This(), comptime fmt: []const u8, args: anytype) !void {
|
||||||
|
var writer = self.buffer.writer();
|
||||||
|
try writer.print(fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeGraphvizHeader(self: *@This()) !void {
|
||||||
|
try self.write("digraph G {{", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeOutGraphViz(self: *@This(), fileName: []const u8) !void {
|
||||||
|
const obuf = try std.fmt.allocPrint(
|
||||||
|
self.allocator,
|
||||||
|
"{s}{s}",
|
||||||
|
.{ self.buffer.items, "}}\n" },
|
||||||
|
);
|
||||||
|
defer self.free(obuf);
|
||||||
|
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const ofile = try std.fmt.allocPrint(self.allocator, core.DefaultSavePath ++ "/{s}", .{fileName});
|
||||||
|
defer self.allocator.free(ofile);
|
||||||
|
|
||||||
|
try cwd.makePath(core.DefaultSavePath);
|
||||||
|
try cwd.writeFile(.{
|
||||||
|
.sub_path = ofile,
|
||||||
|
.data = obuf,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeOut(self: @This(), fileName: []const u8) !void {
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const ofile = try std.fmt.allocPrint(self.allocator, core.DefaultSavePath ++ "/{s}", .{fileName});
|
||||||
|
defer self.allocator.free(ofile);
|
||||||
|
try cwd.makePath(core.DefaultSavePath);
|
||||||
|
try cwd.writeFile(.{
|
||||||
|
.sub_path = ofile,
|
||||||
|
.data = self.buffer.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.buffer.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LoggerSys = struct {
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
writeOutBuffer: std.ArrayList(u8),
|
||||||
|
flushBuffer: std.ArrayList(u8),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
logFilePath: []const u8,
|
||||||
|
logFile: std.fs.File,
|
||||||
|
consoleFile: std.fs.File,
|
||||||
|
lock: std.Thread.Mutex = .{},
|
||||||
|
flushing: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
||||||
|
|
||||||
|
pub fn flush(self: *@This()) !void {
|
||||||
|
var z = tracy.ZoneN(@src(), "Trying to flush");
|
||||||
|
defer z.End();
|
||||||
|
|
||||||
|
self.lock.lock();
|
||||||
|
|
||||||
|
if (!self.flushing.load(.acquire)) {
|
||||||
|
self.flushing.store(true, .seq_cst);
|
||||||
|
const swap = self.writeOutBuffer;
|
||||||
|
self.writeOutBuffer = self.flushBuffer;
|
||||||
|
self.flushBuffer = swap;
|
||||||
|
|
||||||
|
const L = struct {
|
||||||
|
loggerSys: *LoggerSys,
|
||||||
|
|
||||||
|
pub fn func(ctx: @This(), _: *core.JobContext) void {
|
||||||
|
var z1 = tracy.ZoneN(@src(), "flushing output buffer");
|
||||||
|
defer z1.End();
|
||||||
|
ctx.loggerSys.flushFromJob() catch unreachable;
|
||||||
|
ctx.loggerSys.flushing.store(false, .seq_cst);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try core.dispatchJob(L{ .loggerSys = self });
|
||||||
|
} else {
|
||||||
|
//self.flushWriteBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdownFlush(self: *@This()) !void {
|
||||||
|
while (self.flushing.load(.seq_cst)) {}
|
||||||
|
|
||||||
|
// try self.logFile.writer().writeAll(self.writeOutBuffer.items);
|
||||||
|
// try self.consoleFile.writer().writeAll(self.writeOutBuffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flushWriteBuffer(self: *@This()) !void {
|
||||||
|
self.lock.lock();
|
||||||
|
try self.logFile.writer().writeAll(self.writeOutBuffer.items);
|
||||||
|
if (builtin.is_test) {
|
||||||
|
std.debug.print("{s}", .{self.flushBuffer.items});
|
||||||
|
} else {
|
||||||
|
try self.consoleFile.writer().writeAll(self.writeOutBuffer.items);
|
||||||
|
}
|
||||||
|
self.writeOutBuffer.clearRetainingCapacity();
|
||||||
|
self.lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flushFromJob(self: *@This()) !void {
|
||||||
|
self.lock.lock();
|
||||||
|
try self.logFile.writer().writeAll(self.flushBuffer.items);
|
||||||
|
if (builtin.is_test) {
|
||||||
|
std.debug.print("{s}", .{self.flushBuffer.items});
|
||||||
|
} else {
|
||||||
|
try self.consoleFile.writer().writeAll(self.flushBuffer.items);
|
||||||
|
}
|
||||||
|
self.flushBuffer.clearRetainingCapacity();
|
||||||
|
self.lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(self: *@This(), comptime fmt: []const u8, args: anytype) !void {
|
||||||
|
self.lock.lock();
|
||||||
|
try self.writeOutBuffer.writer().print(fmt, args);
|
||||||
|
self.lock.unlock();
|
||||||
|
|
||||||
|
if (self.writeOutBuffer.items.len > LogBufferSize) {
|
||||||
|
if (self.flushBuffer.items.len == 0) {
|
||||||
|
try self.flush();
|
||||||
|
} else {
|
||||||
|
try self.flushWriteBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const ofile = std.fmt.allocPrint(allocator, core.DefaultSavePath ++ "/{s}", .{"Session_Log.txt"}) catch unreachable;
|
||||||
|
cwd.makePath(core.DefaultSavePath) catch unreachable;
|
||||||
|
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = @This(){
|
||||||
|
.allocator = allocator,
|
||||||
|
.writeOutBuffer = std.ArrayList(u8).initCapacity(allocator, LogBufferSize) catch unreachable,
|
||||||
|
.flushBuffer = std.ArrayList(u8).initCapacity(allocator, LogBufferSize) catch unreachable,
|
||||||
|
.logFilePath = ofile,
|
||||||
|
.logFile = cwd.createFile(ofile, .{}) catch unreachable,
|
||||||
|
.consoleFile = std.io.getStdOut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.flushWriteBuffer() catch {};
|
||||||
|
self.allocator.free(self.logFilePath);
|
||||||
|
self.logFile.close();
|
||||||
|
|
||||||
|
self.writeOutBuffer.deinit();
|
||||||
|
self.flushBuffer.deinit();
|
||||||
|
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn processEvents(self: *@This(), frameNumber: u64) core.EngineDataEventError!void {
|
||||||
|
_ = frameNumber;
|
||||||
|
if (self.writeOutBuffer.items.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.flush() catch return error.UnknownStatePanic;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var gFlushForcing: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);
|
||||||
|
|
||||||
|
pub fn forceFlush() void {
|
||||||
|
if (gFlushForcing.load(.seq_cst)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gFlushForcing.store(true, .seq_cst);
|
||||||
|
if (gLoggerSys != null) {
|
||||||
|
gLoggerSys.?.flushWriteBuffer() catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setupLogging(engine: *core.Engine) !void {
|
||||||
|
gLoggerSys = try engine.createObject(LoggerSys, .{
|
||||||
|
.responds_to_events = true,
|
||||||
|
.isCore = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdownLogging() void {
|
||||||
|
if (gLoggerSys) |g| {
|
||||||
|
while (g.flushing.load(.acquire)) {}
|
||||||
|
forceFlush();
|
||||||
|
}
|
||||||
|
gLoggerSys = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLogger() ?*LoggerSys {
|
||||||
|
return gLoggerSys;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
tickables = {}
|
||||||
|
properties = {}
|
||||||
|
|
||||||
|
local function registerTick(object, f)
|
||||||
|
table.insert(tickables, {obj = object, func = f})
|
||||||
|
end
|
||||||
|
|
||||||
|
function __TickScripts(deltaTime)
|
||||||
|
for index, entry in ipairs(tickables) do
|
||||||
|
entry.func(entry.obj, deltaTime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function __RegisterEntityProperty(userdata)
|
||||||
|
properties[userdata] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function SetProperty(entity, property)
|
||||||
|
properties[entity] = property
|
||||||
|
end
|
||||||
|
|
||||||
|
function GetProperty(entity)
|
||||||
|
return properties[entity]
|
||||||
|
end
|
||||||
|
|
||||||
|
function GetObject(entity)
|
||||||
|
return properties[entity]
|
||||||
|
end
|
||||||
|
|
||||||
|
Core = {
|
||||||
|
registerTick = registerTick;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
print("Hello from lua.")
|
||||||
|
|
||||||
|
|
@ -0,0 +1,611 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const misc = @import("misc.zig");
|
||||||
|
const zm = @import("zmath");
|
||||||
|
const math = std.math;
|
||||||
|
|
||||||
|
// LUA BEGIN feel like I should mark up the parts of the engine that need to be
|
||||||
|
// sliced out should i ever decide I'm sick of lua
|
||||||
|
const lua = @import("lua");
|
||||||
|
const pod = lua.pod;
|
||||||
|
// LUA END
|
||||||
|
|
||||||
|
pub const Rayf = RayType(f32);
|
||||||
|
|
||||||
|
pub fn matToScalef(mat: anytype) Vectorf {
|
||||||
|
const x = Vectorf.new(mat[0][0], mat[0][1], mat[0][2]).length();
|
||||||
|
const y = Vectorf.new(mat[1][0], mat[1][1], mat[1][2]).length();
|
||||||
|
const z = Vectorf.new(mat[2][0], mat[2][1], mat[2][2]).length();
|
||||||
|
|
||||||
|
return Vectorf.new(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn RayType(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
start: Vector3Type(T),
|
||||||
|
dir: Vector3Type(T),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp(x: anytype, min: anytype, max: anytype) @TypeOf(x) {
|
||||||
|
if (x < min)
|
||||||
|
return min;
|
||||||
|
if (x > max)
|
||||||
|
return max;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matFromEulerAngles(x: f32, y: f32, z: f32) Mat {
|
||||||
|
return zm.matFromRollPitchYaw(y, z, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Radians(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
value: T,
|
||||||
|
|
||||||
|
pub fn fromDegrees(f: anytype) @This() {
|
||||||
|
return .{ .value = f };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn radians(f: anytype) @TypeOf(f) {
|
||||||
|
return f * math.pi / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fabs(x: anytype) @TypeOf(x) {
|
||||||
|
if (x < 0)
|
||||||
|
return -x;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Vector2Type(comptime T: type, comptime typeName: []const u8) type {
|
||||||
|
return extern struct {
|
||||||
|
x: T = 0,
|
||||||
|
y: T = 0,
|
||||||
|
|
||||||
|
// LUA BEGIN
|
||||||
|
pub const PodDataTable: pod.DataTable = .{
|
||||||
|
.name = typeName,
|
||||||
|
.funcs = &.{
|
||||||
|
"fmul",
|
||||||
|
"dot",
|
||||||
|
"length",
|
||||||
|
"normalize",
|
||||||
|
},
|
||||||
|
.operators = .{
|
||||||
|
.add = "add",
|
||||||
|
.sub = "sub",
|
||||||
|
.mul = "vmul",
|
||||||
|
.eq = "equals",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// LUA END
|
||||||
|
|
||||||
|
pub const Ones = @This(){ .x = 1, .y = 1 };
|
||||||
|
pub const Zeroes = @This(){ .x = 0, .y = 0 };
|
||||||
|
|
||||||
|
pub inline fn new(x: T, y: T) @This() {
|
||||||
|
return .{
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn add(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn sub(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x - other.x,
|
||||||
|
.y = self.y - other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn vmul(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x * other.x,
|
||||||
|
.y = self.y * other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fmul(self: @This(), other: T) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x * other,
|
||||||
|
.y = self.y * other,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn dot(self: @This(), other: @This()) T {
|
||||||
|
return self.x * other.x + self.y * other.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn equals(self: @This(), other: @This()) bool {
|
||||||
|
return self.x == other.x and self.y == other.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn length(self: @This()) T {
|
||||||
|
return std.math.sqrt(self.x * self.x + self.y * self.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleYX(self: @This()) @This() {
|
||||||
|
return .{ .x = self.y, .y = self.x };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn normalize(self: @This()) @This() {
|
||||||
|
if (fabs(self.x) <= 0.0001 and fabs(self.y) <= 0.0001) {
|
||||||
|
return .{ .x = 0, .y = 0 };
|
||||||
|
}
|
||||||
|
const len = std.math.sqrt(self.x * self.x + self.y * self.y);
|
||||||
|
|
||||||
|
if (len < 0.00001)
|
||||||
|
return .{ .x = 0, .y = 0 };
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.x = self.x / len,
|
||||||
|
.y = self.y / len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn from(o: anytype) @This() {
|
||||||
|
const OType: std.builtin.Type = @typeInfo(@TypeOf(o.x));
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Int => {
|
||||||
|
switch (OType) {
|
||||||
|
.Int => {
|
||||||
|
// convert integer into integer
|
||||||
|
return .{
|
||||||
|
.x = @intCast(o.x),
|
||||||
|
.y = @intCast(o.y),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
// convert float into float
|
||||||
|
return .{
|
||||||
|
.x = @intFromFloat(o.x),
|
||||||
|
.y = @intFromFloat(o.y),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
switch (OType) {
|
||||||
|
.Int => {
|
||||||
|
// convert integer into float
|
||||||
|
return .{
|
||||||
|
.x = @floatFromInt(o.x),
|
||||||
|
.y = @floatFromInt(o.y),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
// convert float
|
||||||
|
return .{
|
||||||
|
.x = @floatCast(o.x),
|
||||||
|
.y = @floatCast(o.y),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// convert float into float
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@compileError("Invalid vector conversion");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Vector3Type(comptime T: type, comptime typeName: []const u8) type {
|
||||||
|
return extern struct {
|
||||||
|
x: T = 0,
|
||||||
|
y: T = 0,
|
||||||
|
z: T = 0,
|
||||||
|
|
||||||
|
pub const Ones = @This(){ .x = 1, .y = 1, .z = 1 };
|
||||||
|
pub const Zeroes = @This(){ .x = 0, .y = 0, .z = 0 };
|
||||||
|
|
||||||
|
pub const Up = @This(){ .y = 1 };
|
||||||
|
pub const Right = @This(){ .x = 1 };
|
||||||
|
pub const Forward = @This(){ .z = 1 };
|
||||||
|
|
||||||
|
// LUA BEGIN
|
||||||
|
pub const PodDataTable: pod.DataTable = .{
|
||||||
|
.name = typeName,
|
||||||
|
.funcs = &.{
|
||||||
|
"fmul",
|
||||||
|
"dot",
|
||||||
|
"length",
|
||||||
|
"normalize",
|
||||||
|
},
|
||||||
|
.operators = .{
|
||||||
|
.add = "add",
|
||||||
|
.sub = "sub",
|
||||||
|
.mul = "vmul",
|
||||||
|
.eq = "equals",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// LUA END
|
||||||
|
|
||||||
|
pub inline fn new(x: T, y: T, z: T) @This() {
|
||||||
|
return .{
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
.z = z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fromInt(t: anytype) @This() {
|
||||||
|
return .{ .x = t, .y = t, .z = t };
|
||||||
|
}
|
||||||
|
|
||||||
|
// easing functions
|
||||||
|
pub inline fn drainToZero(self: @This(), o: anytype) @This() {
|
||||||
|
return self.drain(o, Zeroes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn drain(self: @This(), o: anytype, target: anytype) @This() {
|
||||||
|
return .{
|
||||||
|
.x = drainElement(self.x, o.x, target.x),
|
||||||
|
.y = drainElement(self.y, o.y, target.y),
|
||||||
|
.z = drainElement(self.z, o.z, target.z),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn drainElement(a: anytype, v: anytype, t: anytype) @TypeOf(a) {
|
||||||
|
if (a > t) {
|
||||||
|
return @max(t, a - v);
|
||||||
|
} else {
|
||||||
|
return @min(t, a + v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// array functions.
|
||||||
|
pub inline fn toArr3(self: @This()) [3]T {
|
||||||
|
return .{ self.x, self.y, self.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fromArr3(v: [3]f32) @This() {
|
||||||
|
return .{ .x = v[0], .y = v[1], .z = v[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn toZm(self: @This()) zm.Vec {
|
||||||
|
return .{ self.x, self.y, self.z, 1.0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fromZm(vec: zm.Vec) @This() {
|
||||||
|
return @This().new(vec[0], vec[1], vec[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn add(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
.z = self.z + other.z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn sub(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x - other.x,
|
||||||
|
.y = self.y - other.y,
|
||||||
|
.z = self.z - other.z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn vmul(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x * other.x,
|
||||||
|
.y = self.y * other.y,
|
||||||
|
.z = self.z * other.z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fmul(self: @This(), other: T) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x * other,
|
||||||
|
.y = self.y * other,
|
||||||
|
.z = self.z * other,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn dot(self: @This(), other: @This()) T {
|
||||||
|
return self.x * other.x + self.y * other.y + self.z * other.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn equals(self: @This(), other: @This()) bool {
|
||||||
|
return self.x == other.x and self.y == self.y and self.z == self.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn length(self: @This()) T {
|
||||||
|
return std.math.sqrt(self.x * self.x + self.z * self.z + self.y * self.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleYZX(self: @This()) @This() {
|
||||||
|
return .{ .x = self.y, .y = self.z, .z = self.x };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleYXZ(self: @This()) @This() {
|
||||||
|
return .{ .x = self.y, .y = self.x, .z = self.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleZXY(self: @This()) @This() {
|
||||||
|
return .{ .x = self.z, .y = self.x, .z = self.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleZYX(self: @This()) @This() {
|
||||||
|
return .{ .x = self.z, .y = self.y, .z = self.x };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn swizzleXZY(self: @This()) @This() {
|
||||||
|
return .{ .x = self.x, .y = self.z, .z = self.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn lengthXZ(self: @This()) T {
|
||||||
|
return std.math.sqrt(self.x * self.x + self.z * self.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn normalize(self: @This()) @This() {
|
||||||
|
if (fabs(self.x) <= 0.0001 and fabs(self.y) <= 0.0001 and fabs(self.z) <= 0.0001) {
|
||||||
|
return .{ .x = 0, .y = 0, .z = 0 };
|
||||||
|
}
|
||||||
|
const len = std.math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
|
||||||
|
|
||||||
|
if (len < 0.00001)
|
||||||
|
return .{ .x = 0, .y = 0, .z = 0 };
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.x = self.x / len,
|
||||||
|
.y = self.y / len,
|
||||||
|
.z = self.z / len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn fromArray(o: anytype) @This() {
|
||||||
|
return .{
|
||||||
|
.x = o[0],
|
||||||
|
.y = o[1],
|
||||||
|
.z = o[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn clampAllAbs(self: @This(), abs: anytype) @This() {
|
||||||
|
return .{
|
||||||
|
.x = std.math.clamp(self.x, -abs, abs),
|
||||||
|
.y = std.math.clamp(self.y, -abs, abs),
|
||||||
|
.z = std.math.clamp(self.z, -abs, abs),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn cross(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.y * other.z - self.z * other.y,
|
||||||
|
.y = self.z * other.x - self.x * other.z,
|
||||||
|
.z = self.x * other.y - self.y * other.x,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerp(self: @This(), other: @This(), alpha: anytype) @This() {
|
||||||
|
return .{
|
||||||
|
.x = std.math.lerp(self.x, other.x, alpha),
|
||||||
|
.y = std.math.lerp(self.y, other.y, alpha),
|
||||||
|
.z = std.math.lerp(self.z, other.z, alpha),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn clampAll(self: @This(), lower: anytype, upper: anytype) @This() {
|
||||||
|
return .{
|
||||||
|
.x = std.math.clamp(self.x, -lower, upper),
|
||||||
|
.y = std.math.clamp(self.y, -lower, upper),
|
||||||
|
.z = std.math.clamp(self.z, -lower, upper),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn from(o: anytype) @This() {
|
||||||
|
const OType: std.builtin.Type = @typeInfo(@TypeOf(o.x));
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Int => {
|
||||||
|
switch (OType) {
|
||||||
|
.Int => {
|
||||||
|
// convert integer into integer
|
||||||
|
return .{
|
||||||
|
.x = @intCast(o.x),
|
||||||
|
.y = @intCast(o.y),
|
||||||
|
.z = @intCast(o.z),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
// convert float into float
|
||||||
|
return .{
|
||||||
|
.x = @intFromFloat(o.x),
|
||||||
|
.y = @intFromFloat(o.y),
|
||||||
|
.z = @intFromFloat(o.z),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
switch (OType) {
|
||||||
|
.Int => {
|
||||||
|
// convert integer into float
|
||||||
|
return .{
|
||||||
|
.x = @floatFromInt(o.x),
|
||||||
|
.y = @floatFromInt(o.y),
|
||||||
|
.z = @floatFromInt(o.z),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
// convert float
|
||||||
|
return .{
|
||||||
|
.x = @floatCast(o.x),
|
||||||
|
.y = @floatCast(o.y),
|
||||||
|
.z = @floatCast(o.z),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// convert float into float
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
@compileError("Invalid vector type conversion");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@compileError("Invalid vector conversion");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Vector4Type(comptime T: type) type {
|
||||||
|
return extern struct {
|
||||||
|
x: T = 0,
|
||||||
|
y: T = 0,
|
||||||
|
z: T = 0,
|
||||||
|
w: T = 0,
|
||||||
|
|
||||||
|
pub const Ones = @This(){ .x = 1, .y = 1, .z = 1 };
|
||||||
|
pub const Zeroes = @This(){ .x = 0, .y = 0, .z = 0 };
|
||||||
|
|
||||||
|
pub inline fn from(o: anytype) @This() {
|
||||||
|
return .{ .x = o.x, .y = o.y, .z = o.z, .w = o.w };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn new(x: T, y: T, z: T, w: T) @This() {
|
||||||
|
return .{
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
.z = z,
|
||||||
|
.w = w,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn add(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
.z = self.z + other.z,
|
||||||
|
.w = self.w + other.w,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn sub(self: @This(), other: @This()) @This() {
|
||||||
|
return .{
|
||||||
|
.x = self.x - other.x,
|
||||||
|
.y = self.y - other.y,
|
||||||
|
.z = self.z - other.z,
|
||||||
|
.w = self.w - other.w,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn normalize(self: @This()) @This() {
|
||||||
|
const len = std.math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.x = self.x / len,
|
||||||
|
.y = self.y / len,
|
||||||
|
.z = self.z / len,
|
||||||
|
.w = self.w / len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub const Vector4 = Vector4Type(f64);
|
||||||
|
pub const Vector4f = Vector4Type(f32);
|
||||||
|
|
||||||
|
pub const Vector = Vector3Type(f64, "Vector");
|
||||||
|
pub const Vectorf = Vector3Type(f32, "Vectorf");
|
||||||
|
pub const Vector2f = Vector2Type(f32, "Vector2f");
|
||||||
|
pub const Vector2 = Vector2Type(f64, "Vector2");
|
||||||
|
|
||||||
|
pub const Vector2i = Vector2Type(i32, "Vector2i");
|
||||||
|
pub const Vector2c = Vector2Type(c_int, "Vector2c");
|
||||||
|
pub const Vector2u = Vector2Type(u32, "Vector2u");
|
||||||
|
pub const Vector2l = Vector2Type(i64, "Vector2l");
|
||||||
|
|
||||||
|
pub const EulerAngles = Vectorf;
|
||||||
|
pub const Quat = zm.Quat;
|
||||||
|
pub const Mat = zm.Mat;
|
||||||
|
pub const Transform = zm.Mat;
|
||||||
|
|
||||||
|
pub fn matGetPosition(m: Mat) Vectorf {
|
||||||
|
const vec: Vectorf = .{};
|
||||||
|
return Vectorf.fromZm(zm.mul(vec.toZm(), m));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matToRotation(m: Mat) Rotation {
|
||||||
|
const r: Rotation = .{ .quat = zm.matToQuat(m) };
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Rotation = struct {
|
||||||
|
quat: Quat = zm.qidentity(),
|
||||||
|
|
||||||
|
pub fn init() @This() {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eulerX(o: f32) @This() {
|
||||||
|
return .{
|
||||||
|
.quat = zm.matToQuat(zm.rotationX((o))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eulerY(o: f32) @This() {
|
||||||
|
return .{
|
||||||
|
.quat = zm.matToQuat(zm.rotationY((o))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eulerZ(o: f32) @This() {
|
||||||
|
return .{
|
||||||
|
.quat = zm.matToQuat(zm.rotationZ((o))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotateVector(self: @This(), other: Vectorf) Vectorf {
|
||||||
|
return Vectorf.fromZm(
|
||||||
|
zm.mul(
|
||||||
|
other.toZm(),
|
||||||
|
zm.quatToMat(self.quat),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn simdVec4ToVec(vec: zm.Vec) Vector4f {
|
||||||
|
return .{
|
||||||
|
.x = vec[0],
|
||||||
|
.y = vec[1],
|
||||||
|
.z = vec[2],
|
||||||
|
.w = vec[3],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn easeLinear(c: f32, t: f32, dt: f64, rate: f32) f32 {
|
||||||
|
const d = rate * @as(f32, @floatCast(dt));
|
||||||
|
|
||||||
|
if (c > t) {
|
||||||
|
return @max(c - d, t);
|
||||||
|
} else if (c < t) {
|
||||||
|
return @min(c + d, t);
|
||||||
|
} else {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rollingAverage(average: *f64, newValue: f64, sampleCount: f64) void {
|
||||||
|
average.* = average.* - (average.* / sampleCount) + newValue / sampleCount;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Random misc. utilities that aren't /totally/ categorized right now.
|
||||||
|
|
||||||
|
const logging = @import("logging.zig");
|
||||||
|
|
||||||
|
// Creates a for-loopable range between [0, n)
|
||||||
|
pub fn count(comptime n: anytype) [n]u0 {
|
||||||
|
return comptime [1]u0{0} ** n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a for-loopable range between [start, end).
|
||||||
|
// reccomended for use with inline FORs
|
||||||
|
pub fn range(comptime start: usize, comptime end: anytype) [end - start]@TypeOf(start) {
|
||||||
|
comptime var r = [1]@TypeOf(start){start} ** (end - start);
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
for (r, 0..) |val, i| {
|
||||||
|
r[i] = val + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comptime r;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice_to_cstr(str: []const u8) ?[*:0]const u8 {
|
||||||
|
return @as(?[*:0]const u8, @ptrCast(str.ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buf_to_cstr(str: anytype) ?[*:0]const u8 {
|
||||||
|
return @as(?[*:0]const u8, @ptrCast(&str[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CStr = [*:0]const u8;
|
||||||
|
|
||||||
|
pub fn debug_struct(preamble: []const u8, s: anytype) void {
|
||||||
|
logging.graphics_log("{s}:", .{preamble});
|
||||||
|
logging.graphics_log(" {any}", .{s});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn p_to_av(a: anytype) [*]@TypeOf(a.*) {
|
||||||
|
return @as([*]@TypeOf(a.*), @ptrCast(a));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// the module startup system.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// a series of comptime functions for letting you select which features are compiled and brought
|
||||||
|
// into the engine.
|
||||||
|
|
||||||
|
pub const ModuleDescription = struct {
|
||||||
|
name: []const u8,
|
||||||
|
enabledByDefault: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn isModuleEnabled(comptime module: ModuleDescription, comptime buildDescription: anytype) bool {
|
||||||
|
if (!@hasField(@TypeOf(buildDescription), "enabledModules")) {
|
||||||
|
return module.enabledByDefault;
|
||||||
|
} else if (@hasField(@TypeOf(buildDescription.enabledModules), module.name)) {
|
||||||
|
return @field(buildDescription.enabledModules, module.name);
|
||||||
|
} else {
|
||||||
|
return module.enabledByDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "isModule in build test" {
|
||||||
|
const ModA = ModuleDescription{
|
||||||
|
.name = "featureA",
|
||||||
|
.enabledByDefault = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildDesc: struct {
|
||||||
|
enabledModules: struct {
|
||||||
|
featureA: bool = true,
|
||||||
|
} = .{},
|
||||||
|
} = .{};
|
||||||
|
|
||||||
|
std.debug.print("modA enabled = {any}\n", .{isModuleEnabled(ModA, buildDesc)});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const os = std.os;
|
||||||
|
const windows = std.os.windows;
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const lua = @import("lua");
|
||||||
|
|
||||||
|
fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
|
||||||
|
switch (info.ExceptionRecord.ExceptionCode) {
|
||||||
|
windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),
|
||||||
|
windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),
|
||||||
|
windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),
|
||||||
|
windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),
|
||||||
|
else => return windows.EXCEPTION_CONTINUE_SEARCH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var panic_mutex = std.Thread.Mutex{};
|
||||||
|
var panicking = std.atomic.Value(u8).init(0);
|
||||||
|
threadlocal var panic_stage: usize = 0;
|
||||||
|
|
||||||
|
fn waitForOtherThreadToFinishPanicking() void {
|
||||||
|
if (panicking.fetchSub(1, .seq_cst) != 1) {
|
||||||
|
// Another thread is panicking, wait for the last one to finish
|
||||||
|
// and call abort()
|
||||||
|
if (builtin.single_threaded) unreachable;
|
||||||
|
|
||||||
|
// Sleep forever without hammering the CPU
|
||||||
|
var futex = std.atomic.Value(u32).init(0);
|
||||||
|
while (true) std.Thread.Futex.wait(&futex, 0);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleSegfaultWindowsExtra(
|
||||||
|
info: *windows.EXCEPTION_POINTERS,
|
||||||
|
msg: u8,
|
||||||
|
label: ?[]const u8,
|
||||||
|
) noreturn {
|
||||||
|
const exception_address = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
|
||||||
|
core.engine_logs("PANIC!!");
|
||||||
|
core.forceFlush();
|
||||||
|
|
||||||
|
if (!@hasDecl(windows, "CONTEXT")) {
|
||||||
|
switch (msg) {
|
||||||
|
0 => {
|
||||||
|
dumpSegfaultInfoWindows(info, msg, label);
|
||||||
|
std.posix.abort();
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
const format_item = "Segmentation fault at address 0x{x}";
|
||||||
|
var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large
|
||||||
|
const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
|
||||||
|
std.debug.panicImpl(null, exception_address, to_print);
|
||||||
|
},
|
||||||
|
2 => std.debug.panicImpl(null, exception_address, "Illegal Instruction"),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dumpSegfaultInfoWindows(info, msg, label);
|
||||||
|
std.posix.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
_ = switch (msg) {
|
||||||
|
0 => stderr.print("{s}\n", .{label.?}),
|
||||||
|
1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
|
||||||
|
2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}),
|
||||||
|
else => unreachable,
|
||||||
|
} catch std.posix.abort();
|
||||||
|
|
||||||
|
std.debug.dumpStackTraceFromBase(info.ContextRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dumpStackPointerAddr(prefix: []const u8) void {
|
||||||
|
const sp = asm (""
|
||||||
|
: [argc] "={rsp}" (-> usize),
|
||||||
|
);
|
||||||
|
std.debug.print("{} sp = 0x{x}\n", .{ prefix, sp });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
_ = switch (sig) {
|
||||||
|
std.posix.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
|
||||||
|
std.posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
|
||||||
|
std.posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
|
||||||
|
std.posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
|
||||||
|
else => unreachable,
|
||||||
|
} catch std.posix.abort();
|
||||||
|
|
||||||
|
switch (builtin.cpu.arch) {
|
||||||
|
.x86,
|
||||||
|
.x86_64,
|
||||||
|
.arm,
|
||||||
|
.aarch64,
|
||||||
|
=> {
|
||||||
|
const ctx: *const std.c.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||||
|
std.debug.dumpStackTraceFromBase(ctx);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn {
|
||||||
|
core.engine_logs("PANIC!!");
|
||||||
|
core.forceFlush();
|
||||||
|
resetSegfaultHandler();
|
||||||
|
|
||||||
|
const addr = switch (builtin.os.tag) {
|
||||||
|
.linux => @intFromPtr(info.fields.sigfault.addr),
|
||||||
|
.freebsd, .macos => @intFromPtr(info.addr),
|
||||||
|
.netbsd => @intFromPtr(info.info.reason.fault.addr),
|
||||||
|
.openbsd => @intFromPtr(info.data.fault.addr),
|
||||||
|
.solaris => @intFromPtr(info.reason.fault.addr),
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
nosuspend switch (panic_stage) {
|
||||||
|
0 => {
|
||||||
|
panic_stage = 1;
|
||||||
|
_ = panicking.fetchAdd(1, .seq_cst);
|
||||||
|
|
||||||
|
{
|
||||||
|
panic_mutex.lock();
|
||||||
|
defer panic_mutex.unlock();
|
||||||
|
|
||||||
|
dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForOtherThreadToFinishPanicking();
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// panic mutex already locked
|
||||||
|
dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// We cannot allow the signal handler to return because when it runs the original instruction
|
||||||
|
// again, the memory may be mapped and undefined behavior would occur rather than repeating
|
||||||
|
// the segfault. So we simply abort here.
|
||||||
|
std.posix.abort();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
fn resetSegfaultHandler() void {
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
if (windows_segfault_handle) |handle| {
|
||||||
|
_ = windows.kernel32.RemoveVectoredExceptionHandler(handle);
|
||||||
|
windows_segfault_handle = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var act = std.c.Sigaction{
|
||||||
|
.handler = .{ .handler = std.posix.SIG.DFL },
|
||||||
|
.mask = std.posix.empty_sigset,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
// To avoid a double-panic, do nothing if an error happens here.
|
||||||
|
std.debug.updateSegfaultHandler(&act) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// my custom version of segfault handler.
|
||||||
|
var windows_segfault_handle: ?windows.HANDLE = null;
|
||||||
|
|
||||||
|
pub fn attachSegfaultHandler() void {
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var act = std.c.Sigaction{
|
||||||
|
.handler = .{ .sigaction = handleSegfaultPosix },
|
||||||
|
.mask = std.posix.empty_sigset,
|
||||||
|
.flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART | std.posix.SA.RESETHAND),
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.updateSegfaultHandler(&act) catch {
|
||||||
|
@panic("unable to install segfault handler, maybe adjust have_segfault_handling_support in std/debug.zig");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// TODO --- this should become it's own engine module
|
||||||
|
const std = @import("std");
|
||||||
|
const spng = @import("spng");
|
||||||
|
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
pub const PngContents = struct {
|
||||||
|
path: []const u8,
|
||||||
|
pixels: []u8,
|
||||||
|
size: core.Vector2u,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn initFromPathSpec(path: []const u8, allocator: std.mem.Allocator) !@This() {
|
||||||
|
var pngContents: PngContents = undefined;
|
||||||
|
var cookedPath = std.ArrayList(u8).init(allocator);
|
||||||
|
defer cookedPath.deinit();
|
||||||
|
|
||||||
|
try cookedPath.appendSlice("_cooked/");
|
||||||
|
try cookedPath.appendSlice(path);
|
||||||
|
try cookedPath.appendSlice(".Texture");
|
||||||
|
|
||||||
|
if (core.fs().fileExists(cookedPath.items)) {
|
||||||
|
pngContents = try PngContents.initFromFSCooked(core.fs(), allocator, cookedPath.items);
|
||||||
|
} else {
|
||||||
|
pngContents = try PngContents.initFromFS(core.fs(), allocator, path);
|
||||||
|
}
|
||||||
|
return pngContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initFromFSCooked(fs: *core.FileSystem, allocator: std.mem.Allocator, path: []const u8) !@This() {
|
||||||
|
const mapping = try fs.loadFile(path);
|
||||||
|
defer fs.unmap(mapping);
|
||||||
|
|
||||||
|
core.engine_log("loading cooked version of file: {s}", .{path});
|
||||||
|
|
||||||
|
const rv: @This() = .{
|
||||||
|
.size = .{
|
||||||
|
.x = @as(u32, @bitCast(mapping.bytes[0..4].*)),
|
||||||
|
.y = @as(u32, @bitCast(mapping.bytes[4..8].*)),
|
||||||
|
},
|
||||||
|
.path = try allocator.dupe(u8, path),
|
||||||
|
.pixels = try allocator.dupe(u8, mapping.bytes[8..]),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toBuffer(self: @This()) !std.ArrayList(u8) {
|
||||||
|
var buffer = std.ArrayList(u8).init(self.allocator);
|
||||||
|
try buffer.appendSlice(&@as([4]u8, @bitCast(self.size.x)));
|
||||||
|
try buffer.appendSlice(&@as([4]u8, @bitCast(self.size.y)));
|
||||||
|
try buffer.appendSlice(self.pixels);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initFromFS(fs: *core.FileSystem, allocator: std.mem.Allocator, path: []const u8) !@This() {
|
||||||
|
// 1. check the fs to see if a cooked version of the file exists
|
||||||
|
// 2. load that one if possible
|
||||||
|
// 3. otherwise, load the other one.
|
||||||
|
|
||||||
|
const mapping = try fs.loadFile(path);
|
||||||
|
defer fs.unmap(mapping);
|
||||||
|
|
||||||
|
return try initFromBytes(allocator, path, mapping.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initFromBytes(allocator: std.mem.Allocator, pathName: []const u8, pngFileContents: []const u8) !@This() {
|
||||||
|
var decoder = try spng.SpngContext.newDecoder();
|
||||||
|
defer decoder.deinit();
|
||||||
|
|
||||||
|
try decoder.setBuffer(pngFileContents);
|
||||||
|
const header = try decoder.getHeader();
|
||||||
|
|
||||||
|
const imageSize = @as(usize, @intCast(header.width * header.height * 4));
|
||||||
|
const pixels: []u8 = try allocator.alloc(u8, imageSize);
|
||||||
|
const len = try decoder.decode(pixels, spng.SPNG_FMT_RGBA8, spng.SPNG_DECODE_TRNS);
|
||||||
|
try core.assertf(len == pixels.len, "decoded pixel size not buffer size {d} != {d}", .{ len, pixels.len });
|
||||||
|
|
||||||
|
return PngContents{
|
||||||
|
.path = try core.dupe(u8, allocator, pathName),
|
||||||
|
.pixels = pixels,
|
||||||
|
.size = .{ .x = header.width, .y = header.height },
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.allocator.free(self.path);
|
||||||
|
self.allocator.free(self.pixels);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,407 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
||||||
|
const tracy = core.tracy;
|
||||||
|
|
||||||
|
pub const Transform = core.Mat;
|
||||||
|
const SceneObjectSet = core.SparseMultiSet(SceneObject);
|
||||||
|
const SceneSet = core.SparseSet(Scene);
|
||||||
|
|
||||||
|
pub const SceneAttachMode = enum {
|
||||||
|
none, // default, parent attachment is irrelevant and not used
|
||||||
|
relativePositionOnly, // only position is relative to parent transform
|
||||||
|
relativeRotationOnly, // only rotation is relative to parent transform
|
||||||
|
relative, // position and rotation are relative to parent transform
|
||||||
|
snapToParentPositionOnly, // snaps to the parent's position, maintaining independent rotation (not implemented)
|
||||||
|
snapToParentRotationOnly, // copys the parent's rotation, maintaining independent rotation (not implemented)
|
||||||
|
snapToParent, // snaps to parent rotation and position
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneMobilityMode = enum {
|
||||||
|
static, // scene is updated once and never again
|
||||||
|
moveable, // sceneobject is moveable and has it's final transform updated
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneObjectPosRot = struct {
|
||||||
|
position: core.Vectorf = .{ .x = 0, .y = 0, .z = 0 },
|
||||||
|
rotation: core.Rotation = core.Rotation.init(),
|
||||||
|
scale: core.Vectorf = core.Vectorf.new(1.0, 1.0, 1.0),
|
||||||
|
|
||||||
|
pub inline fn toTransform(self: @This()) core.Transform {
|
||||||
|
var transform = core.zm.mul(
|
||||||
|
core.zm.scalingV(self.scale.toZm()),
|
||||||
|
core.zm.matFromQuat(self.rotation.quat),
|
||||||
|
);
|
||||||
|
|
||||||
|
transform = core.zm.mul(
|
||||||
|
transform,
|
||||||
|
core.zm.translationV(self.position.toZm()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneObjectSettings = struct {
|
||||||
|
attachmentMode: SceneAttachMode = .none,
|
||||||
|
sceneMode: SceneMobilityMode = .static,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneObjectRepr = struct {
|
||||||
|
// fields intended to be internally used, don't touch them
|
||||||
|
// unless you know what you're doing
|
||||||
|
transform: core.Mat = core.zm.identity(), // relative transform against parent, if no parent, then this is world transform
|
||||||
|
parent: ?core.ObjectHandle = null,
|
||||||
|
attachmentMode: SceneAttachMode = .relative, // doesn't do anything yet, only support relative right now
|
||||||
|
transformOverride: ?*core.Transform = null,
|
||||||
|
lastUpdate: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneObject = struct {
|
||||||
|
_repr: SceneObjectRepr = .{}, // not public
|
||||||
|
posRot: SceneObjectPosRot = .{}, // position and rotation
|
||||||
|
settings: SceneObjectSettings = .{}, //
|
||||||
|
children: ArrayListUnmanaged(core.ObjectHandle) = .{},
|
||||||
|
|
||||||
|
pub fn init(params: SceneObjectInitParams) @This() {
|
||||||
|
// Hmm thinking in the future we could have scene objects be f64s then crunch them down to f32s when we are submiting to gpu
|
||||||
|
var self = @This(){
|
||||||
|
.posRot = .{},
|
||||||
|
._repr = .{},
|
||||||
|
.settings = .{},
|
||||||
|
.children = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldUpdate: bool = false; // should mutate
|
||||||
|
|
||||||
|
switch (params) {
|
||||||
|
.transform => {
|
||||||
|
self._repr.transform = params.transform;
|
||||||
|
self.posRot.position = core.Vectorf.fromZm(core.zm.mul(params.transform, core.Vectorf.zero().toZm()));
|
||||||
|
self.posRot.rotation = .{ .quat = core.zm.matToQuat(params.transform) };
|
||||||
|
},
|
||||||
|
.position => {
|
||||||
|
@panic("todo: implement position only initialization");
|
||||||
|
},
|
||||||
|
.rotation => {
|
||||||
|
@panic("todo: implement rotation only initialization");
|
||||||
|
},
|
||||||
|
.positionRotAngles => {
|
||||||
|
@panic("todo: implement position + rotation initialization (angles) ");
|
||||||
|
},
|
||||||
|
.positionRot => {
|
||||||
|
@panic("todo: implement position + rotation initialization");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Scene = struct {
|
||||||
|
handle: core.ObjectHandle = .{ .generation = 0, .index = 0, .alive = false },
|
||||||
|
|
||||||
|
pub var BaseContainer: *SceneSet = undefined;
|
||||||
|
pub var SceneObjectContainer: *SceneObjectSet = undefined;
|
||||||
|
pub const ComponentName = "Scene";
|
||||||
|
|
||||||
|
pub const ScriptExports: []const []const u8 = &.{
|
||||||
|
"setPosition",
|
||||||
|
"setRotation",
|
||||||
|
"setScale",
|
||||||
|
"setScaleV",
|
||||||
|
"getPosition",
|
||||||
|
"getRotation",
|
||||||
|
"getParent",
|
||||||
|
"printHandleIndex",
|
||||||
|
// "getTransform", not implemented yet
|
||||||
|
// "setMobility", gonna need special setup for this one
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn initECS(self: *@This(), handle: core.ObjectHandle) void {
|
||||||
|
self.handle = handle;
|
||||||
|
_ = SceneObjectContainer.createWithHandleECS(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printHandleIndex(self: @This()) void {
|
||||||
|
core.engine_log("handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPosition(self: @This(), position: core.Vectorf) void {
|
||||||
|
if (SceneObjectContainer.get(self.handle, .posRot)) |posRot| {
|
||||||
|
posRot.*.position = position;
|
||||||
|
// core.engine_log("setposition scucess handle.index = 0x{x}", .{self.handle.index});
|
||||||
|
} else {
|
||||||
|
core.engine_log("setposition failed handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPosRot(self: *@This()) *SceneObjectPosRot {
|
||||||
|
return SceneObjectContainer.get(self.handle, .posRot).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotation(self: @This(), rotation: core.Rotation) void {
|
||||||
|
SceneObjectContainer.get(self.handle, .posRot).?.*.rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setScale(self: @This(), x: f32, y: f32, z: f32) void {
|
||||||
|
SceneObjectContainer.get(self.handle, .posRot).?.*.scale = .{ .x = x, .y = y, .z = z };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setScaleV(self: @This(), scale: core.Vectorf) void {
|
||||||
|
SceneObjectContainer.get(self.handle, .posRot).?.*.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPosition(self: @This()) core.Vectorf {
|
||||||
|
// core.engine_logs("getPosition called");
|
||||||
|
return SceneObjectContainer.get(self.handle, .posRot).?.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRotation(self: @This()) core.Rotation {
|
||||||
|
return SceneObjectContainer.get(self.handle, .posRot).?.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getScaleV(self: @This()) core.Vectorf {
|
||||||
|
return SceneObjectContainer.get(self.handle, .posRot).?.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getParent(self: @This()) core.Entity {
|
||||||
|
return core.Entity{ .handle = SceneObjectContainer.get(self.handle, ._repr).?.parent orelse .{} };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setParent(self: @This(), newParent: core.Entity) void {
|
||||||
|
core.engine_log("parenting entity {d} -> {d}", .{ self.handle.index, newParent.handle.index });
|
||||||
|
const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?;
|
||||||
|
const thisParent = repr.parent;
|
||||||
|
if (thisParent) |p| {
|
||||||
|
const parentRef = @This(){ .handle = p };
|
||||||
|
parentRef.removeChild(self.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
repr.parent = newParent.handle;
|
||||||
|
|
||||||
|
const children = SceneObjectContainer.get(newParent.handle, .children).?;
|
||||||
|
children.append(childAllocator(), newParent.handle) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearParent(self: @This()) void {
|
||||||
|
const repr = SceneObjectContainer.get(self.handle, ._repr).?;
|
||||||
|
const thisParent = &repr.parent;
|
||||||
|
if (thisParent) |p| {
|
||||||
|
const parentRef = @This(){ .handle = p };
|
||||||
|
parentRef.removeChild(self.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
repr.parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeChild(self: @This(), child: core.ObjectHandle) void {
|
||||||
|
const children = SceneObjectContainer.get(self.handle, .children).?;
|
||||||
|
for (children.items, 0..) |search, i| {
|
||||||
|
if (child.eql(search)) {
|
||||||
|
_ = children.swapRemove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getAndResolveTransform(self: @This()) core.Transform {
|
||||||
|
const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?;
|
||||||
|
if (repr.lastUpdate != gSceneSystem.tickCount) {
|
||||||
|
gSceneSystem.updateTransform(repr, SceneObjectContainer.get(self.handle, .posRot).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
return repr.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTransform(self: @This()) core.Transform {
|
||||||
|
return SceneObjectContainer.get(self.handle, ._repr).?.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// you MUST clearTransfomRefUnsafe() before destroying this transform
|
||||||
|
pub fn setTransformRefUnsafe(self: @This(), ref: *core.Transform) void {
|
||||||
|
SceneObjectContainer.get(self.handle, ._repr).?.transformOverride = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearTransformRefUnsafe(self: @This(), ref: *core.Transform) void {
|
||||||
|
SceneObjectContainer.get(self.handle, ._repr).?.transformOverride = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMobility(self: @This(), mobility: SceneMobilityMode) void {
|
||||||
|
const settings = Scene.SceneObjectContainer.get(self.handle, .settings).?;
|
||||||
|
if (settings.sceneMode == .static) {
|
||||||
|
if (mobility == .moveable) {
|
||||||
|
core.gScene.dynamicObjects.append(core.gScene.allocator, self.handle) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.sceneMode == .moveable) {
|
||||||
|
if (mobility == .static) {
|
||||||
|
@panic("todo unable to change scene mobility back to static");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.*.sceneMode = mobility;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SceneObjectInitParams = union(enum) {
|
||||||
|
transform: core.Transform,
|
||||||
|
position: core.Vectorf,
|
||||||
|
rotation: core.Quat,
|
||||||
|
positionRotAngles: struct {
|
||||||
|
position: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
|
||||||
|
angles: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
|
||||||
|
},
|
||||||
|
positionRot: struct {
|
||||||
|
position: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
|
||||||
|
angles: core.Quat = core.zm.qidentity(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var gSceneSystem: *SceneSystem = undefined;
|
||||||
|
|
||||||
|
fn childAllocator() std.mem.Allocator {
|
||||||
|
return gSceneSystem.childrenArena.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SceneSystem = struct {
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
dynamicObjects: ArrayListUnmanaged(core.ObjectHandle) = .{},
|
||||||
|
childrenArena: std.heap.ArenaAllocator,
|
||||||
|
tickCount: u32 = 0,
|
||||||
|
|
||||||
|
pub const Field = SceneObjectSet.Field;
|
||||||
|
pub const FieldType = SceneObjectSet.FieldType;
|
||||||
|
|
||||||
|
// internal update transform function
|
||||||
|
fn updateTransform(self: *@This(), repr: *SceneObjectRepr, posRot: *const SceneObjectPosRot) void {
|
||||||
|
if (repr.lastUpdate == self.tickCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repr.transformOverride) |override| {
|
||||||
|
repr.lastUpdate = self.tickCount;
|
||||||
|
repr.transform = override.*;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var final: core.Transform = core.zm.identity();
|
||||||
|
|
||||||
|
if (repr.parent) |parent| {
|
||||||
|
if (Scene.SceneObjectContainer.get(parent, ._repr)) |parentRepr| {
|
||||||
|
const parentPosRot = Scene.SceneObjectContainer.get(parent, .posRot).?;
|
||||||
|
self.updateTransform(parentRepr, parentPosRot);
|
||||||
|
const parentTransform = parentRepr.transform;
|
||||||
|
final = core.zm.mul(parentTransform, final);
|
||||||
|
} else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
|
||||||
|
// final[0][0],
|
||||||
|
// final[0][1],
|
||||||
|
// final[0][2],
|
||||||
|
// final[0][3],
|
||||||
|
// final[1][0],
|
||||||
|
// final[1][1],
|
||||||
|
// final[1][2],
|
||||||
|
// final[1][3],
|
||||||
|
// });
|
||||||
|
repr.transform = core.zm.mul(
|
||||||
|
core.zm.mul(
|
||||||
|
core.zm.mul(
|
||||||
|
core.zm.scalingV(posRot.scale.toZm()),
|
||||||
|
core.zm.matFromQuat(posRot.rotation.quat),
|
||||||
|
),
|
||||||
|
core.zm.translationV(posRot.position.toZm()),
|
||||||
|
),
|
||||||
|
final,
|
||||||
|
);
|
||||||
|
|
||||||
|
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
|
||||||
|
// repr.transform[0][0],
|
||||||
|
// repr.transform[0][1],
|
||||||
|
// repr.transform[0][2],
|
||||||
|
// repr.transform[0][3],
|
||||||
|
// repr.transform[1][0],
|
||||||
|
// repr.transform[1][1],
|
||||||
|
// repr.transform[1][2],
|
||||||
|
// repr.transform[1][3],
|
||||||
|
// });
|
||||||
|
|
||||||
|
repr.lastUpdate = self.tickCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateTransforms(self: *@This()) void {
|
||||||
|
// todo. calculate a running load factor for the number of movable objects
|
||||||
|
// vs static objects
|
||||||
|
// if we have a small amount of movable vs static AND if we have > 1000 objects,
|
||||||
|
// then iterate over dynamicObjects array instead
|
||||||
|
for (Scene.SceneObjectContainer.denseItems(._repr), 0..) |*repr, i| {
|
||||||
|
const settings = Scene.SceneObjectContainer.readDense(i, .settings);
|
||||||
|
if (settings.sceneMode == .moveable or repr.lastUpdate == 0) {
|
||||||
|
const posRot = Scene.SceneObjectContainer.readDense(i, .posRot);
|
||||||
|
self.updateTransform(repr, posRot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- NeonObject interace ----
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.childrenArena = std.heap.ArenaAllocator.init(allocator),
|
||||||
|
};
|
||||||
|
gSceneSystem = self;
|
||||||
|
try core.defineComponent(Scene, allocator);
|
||||||
|
Scene.SceneObjectContainer = try SceneObjectSet.create(allocator);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preTick(self: *@This(), dt: f64) !void {
|
||||||
|
_ = dt;
|
||||||
|
self.tickCount +%= 1;
|
||||||
|
if (self.tickCount == 0) {
|
||||||
|
self.tickCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), deltaTime: f64) void {
|
||||||
|
var z = tracy.ZoneNC(@src(), "Scene System Tick", 0xAABBDD);
|
||||||
|
defer z.End();
|
||||||
|
self.updateTransforms();
|
||||||
|
_ = deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.dynamicObjects.deinit(self.allocator);
|
||||||
|
self.childrenArena.deinit();
|
||||||
|
core.undefineComponent(Scene);
|
||||||
|
Scene.SceneObjectContainer.destroy();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// LUA_BEGIN
|
||||||
|
|
||||||
|
// because scene objects are a special sparse-multiset type,
|
||||||
|
// they do not have a fixed representation in the sparse set.
|
||||||
|
// as a result this type requires a special implementation to operate properly.
|
||||||
|
// multi-set systems should only ever modify values via functions
|
||||||
|
|
||||||
|
// we really need a way to deal with multi-set handles.
|
||||||
|
// idea - in the component registration. if the container type is a sparse multiset
|
||||||
|
// then the pointer type shall be a pointer to the set handle.
|
||||||
|
// and the component acquisition shall do absolutely nothing but grab the sparse index of the set handle
|
||||||
|
//
|
||||||
|
// man... that shit sounds like so much work...
|
||||||
|
|
||||||
|
// LUA_END
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
// scripting integration using lua
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
pub const lua = @import("lua");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const startup_script = @embedFile("lua/startup.lua");
|
||||||
|
const core_script = @embedFile("lua/core.lua");
|
||||||
|
const ecs = @import("ecs.zig");
|
||||||
|
const ComponentRef = @import("script/ComponentRef.zig");
|
||||||
|
const ComponentRegistration = @import("script/ComponentRegistration.zig");
|
||||||
|
|
||||||
|
pub const script_bindings = @import("script_bindings.zig");
|
||||||
|
|
||||||
|
const c = lua.c;
|
||||||
|
|
||||||
|
var gLuaState: lua.LuaState = undefined;
|
||||||
|
var gLuaAllocator: std.mem.Allocator = undefined;
|
||||||
|
|
||||||
|
const luaRegLibs: []const c.luaL_Reg = &.{
|
||||||
|
.{ .name = "print", .func = printWrapper },
|
||||||
|
.{ .name = null, .func = null },
|
||||||
|
};
|
||||||
|
|
||||||
|
// binds the default print() function in lua to print to the console
|
||||||
|
fn printWrapper(l: ?*lua.c.lua_State) callconv(.C) i32 {
|
||||||
|
const state: lua.LuaState = .{ .l = l };
|
||||||
|
const argc = state.getTop();
|
||||||
|
|
||||||
|
if (core.getLogger() == null) {
|
||||||
|
core.printRaw("> ", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc >= 1) {
|
||||||
|
core.printRaw("[SCRIPT ]: ", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: i32 = 1;
|
||||||
|
while (i <= argc) : (i += 1) {
|
||||||
|
if (state.isString(i)) {
|
||||||
|
if (i > 1) {
|
||||||
|
core.printRaw(" ", .{});
|
||||||
|
}
|
||||||
|
core.printRaw("{s}", .{state.toString(i)});
|
||||||
|
} else if (state.isUserdata(i)) {
|
||||||
|
const s = state.toStringL(i);
|
||||||
|
state.pop(1);
|
||||||
|
core.printRaw("{s}", .{s});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.printRaw("\n", .{});
|
||||||
|
state.pop(argc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialization of the lua scripting interface.
|
||||||
|
// this interface is only threadsafe to operate on from the main systems thread (at this time).
|
||||||
|
pub fn start_lua(allocator: std.mem.Allocator) !void {
|
||||||
|
gLuaAllocator = allocator;
|
||||||
|
gLuaState = try lua.LuaState.init(.{});
|
||||||
|
try lua.pod.setupFormatBuffer(allocator);
|
||||||
|
|
||||||
|
// overload and hook into global functions
|
||||||
|
_ = gLuaState.getGlobal("_G");
|
||||||
|
c.luaL_setfuncs(gLuaState.l, luaRegLibs.ptr, 0);
|
||||||
|
gLuaState.pop(1);
|
||||||
|
|
||||||
|
try lua.pod.registerPodType(&gLuaState, ecs.Entity);
|
||||||
|
try lua.pod.registerPodType(&gLuaState, ComponentRegistration);
|
||||||
|
|
||||||
|
try script_bindings.registerTypes();
|
||||||
|
|
||||||
|
try gLuaState.loadString(startup_script);
|
||||||
|
try gLuaState.pcall();
|
||||||
|
|
||||||
|
try gLuaState.loadString(core_script);
|
||||||
|
try gLuaState.pcall();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createLuaComponentDefinitions(globalName: []const u8, container: ecs.EcsContainerRef, luaNew: anytype) !void {
|
||||||
|
const reg = try gLuaState.newZigUserdata(ComponentRegistration);
|
||||||
|
reg.ref = container;
|
||||||
|
reg.name = globalName;
|
||||||
|
reg.luaNew = luaNew;
|
||||||
|
try gLuaState.setGlobal(globalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerComponent(comptime Component: type, container: ecs.EcsContainerRef) !void {
|
||||||
|
const ReferenceType = ComponentRef.ComponentReferenceType(Component);
|
||||||
|
try createLuaComponentDefinitions(@ptrCast(Component.ComponentName), container, ReferenceType.luaNew);
|
||||||
|
try ReferenceType.registerType(getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_lua() void {
|
||||||
|
lua.pod.shutdownFormatBuffer();
|
||||||
|
gLuaState.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getState() *lua.LuaState {
|
||||||
|
return &gLuaState;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findScriptBasepath(s: []const u8) []const u8 {
|
||||||
|
var i: usize = s.len - 1;
|
||||||
|
|
||||||
|
while (i > 0) : (i -= 1) {
|
||||||
|
if (s[i] == '\\' or s[i] == '/') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s[i] == '\\' or s[i] == '/') {
|
||||||
|
return s[i + 1 ..];
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadTypes(scriptPath: []const u8) !void {
|
||||||
|
// 1. scan filesystem for all files under the script path
|
||||||
|
var fileList = try core.fs().listAllSubpaths(gLuaAllocator, scriptPath);
|
||||||
|
defer fileList.deinit();
|
||||||
|
for (fileList.data.items, 0..) |f, i| {
|
||||||
|
// 2. enforce naming scheme for each script.
|
||||||
|
const basePath = findScriptBasepath(f);
|
||||||
|
|
||||||
|
// core.engine_log("checking script file: {s} path:{s} base {s}", .{ f, fileList.sources.items[i], basePath });
|
||||||
|
// 3. for each one, load the script and assign them based on name.
|
||||||
|
if (std.ascii.isUpper(basePath[0])) {
|
||||||
|
core.engine_log("loading script file: {s} path:{s}", .{ f, fileList.sources.items[i] });
|
||||||
|
const scriptFile = try core.fs().loadFile(f);
|
||||||
|
defer core.fs().unmap(scriptFile);
|
||||||
|
try gLuaState.loadString(scriptFile.bytes);
|
||||||
|
try gLuaState.pcall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runScriptFile(scriptPath: []const u8) !void {
|
||||||
|
const scriptFile = try core.fs().loadFile(scriptPath);
|
||||||
|
defer core.fs().unmap(scriptFile);
|
||||||
|
try gLuaState.loadString(scriptFile.bytes);
|
||||||
|
try gLuaState.pcall();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
// lwdt can't have meta tables so I'm going to create component references
|
||||||
|
//
|
||||||
|
// the way this works is
|
||||||
|
// - entities are created as POD types
|
||||||
|
// - entities can get components added to them via entity:addComponent
|
||||||
|
// - this returns a ComponentReference
|
||||||
|
// - you can also get components from an entity via enity:get()
|
||||||
|
// - this also returns a ComponentReference
|
||||||
|
//
|
||||||
|
// - ComponentReferences allow you to modify data on a component or call functions on them
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// How the registration works
|
||||||
|
//
|
||||||
|
// - define component
|
||||||
|
// - ecs.zig defineComponent
|
||||||
|
// - ComponentRef.zig - ReferenceType
|
||||||
|
// - addComponentRegistration - script.zig
|
||||||
|
// - ComponentRef - ReferenceType.registerType
|
||||||
|
|
||||||
|
// this represents the lua side of the object
|
||||||
|
pub fn ComponentReferenceType(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
ptr: *T = undefined,
|
||||||
|
|
||||||
|
// used for resolving deltas.
|
||||||
|
handle: core.ObjectHandle = undefined,
|
||||||
|
stateCount: u32 = 0,
|
||||||
|
containerRef: ecs.EcsContainerRef = undefined,
|
||||||
|
|
||||||
|
pub const MetatableName = T.ComponentName;
|
||||||
|
|
||||||
|
// argc = 1,
|
||||||
|
// 1. a componentRegistration userdata
|
||||||
|
// can only be called from entity.luaAddComponent
|
||||||
|
pub fn luaNew(state: lua.LuaState, handle: core.ObjectHandle, ptr: ?*anyopaque) void {
|
||||||
|
const ud = state.newZigUserdata(@This()) catch return;
|
||||||
|
const containerRef = ecs.getTypeContainer(T);
|
||||||
|
|
||||||
|
ud.* = .{
|
||||||
|
.handle = handle,
|
||||||
|
.containerRef = containerRef,
|
||||||
|
.stateCount = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// std.debug.print("luaNew ComponentReferenceType: {x}\n", .{@intFromPtr(ud)});
|
||||||
|
// std.debug.print("luaNew ContainerRef: {x}\n", .{@intFromPtr(containerRef.ptr)});
|
||||||
|
// std.debug.print("luaNew " ++ @typeName(T) ++ " ud.ptr = {x}\n", .{@intFromPtr(ptr)});
|
||||||
|
|
||||||
|
if (ptr) |p| {
|
||||||
|
ud.ptr = @ptrCast(@alignCast(p));
|
||||||
|
ud.stateCount = containerRef.vtable.getStateCount(containerRef.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) {
|
||||||
|
// ... there are several things that need to be reworked here...
|
||||||
|
ud.ptr = @ptrCast(@alignCast(@as(*anyopaque, @ptrCast(&ud.handle))));
|
||||||
|
// std.debug.print("luaNew " ++ @typeName(T) ++ " v = {any}\n", .{ud.ptr.getPosition()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(self: *@This()) void {
|
||||||
|
const ref = self.containerRef;
|
||||||
|
// std.debug.print("self: {x}\n", .{@intFromPtr(self)});
|
||||||
|
// std.debug.print("ptr: {x}\n", .{@intFromPtr(ref.ptr)});
|
||||||
|
if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
if (ref.vtable.getStateCount(ref.ptr) != self.stateCount) {
|
||||||
|
self.ptr = @ptrCast(@alignCast(ref.vtable.get(ref.ptr, self.handle)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *@This()) *T {
|
||||||
|
self.resolve();
|
||||||
|
return self.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn luaToString(state: lua.LuaState) i32 {
|
||||||
|
// oh god... this isn't good
|
||||||
|
// I think i've been treating this component ref as the user type
|
||||||
|
// huge failure of type resolution
|
||||||
|
if (state.toUserdata(@This(), 1)) |self| {
|
||||||
|
self.resolve();
|
||||||
|
Buffer.clearRetainingCapacity();
|
||||||
|
|
||||||
|
var writer = Buffer.writer();
|
||||||
|
writer.print("{s}{{", .{MetatableName}) catch return 0;
|
||||||
|
|
||||||
|
inline for (std.meta.fields(T), 0..) |field, i| {
|
||||||
|
if (i == 0) {
|
||||||
|
writer.print(" {s} = ", .{field.name}) catch return 0;
|
||||||
|
} else {
|
||||||
|
writer.print(", {s} = ", .{field.name}) catch return 0;
|
||||||
|
}
|
||||||
|
switch (field.type) {
|
||||||
|
f32, i32, u32, f64, i64, u64 => {
|
||||||
|
writer.print("{d}", .{@field(self.ptr, field.name)}) catch return 0;
|
||||||
|
},
|
||||||
|
[]const u8 => {
|
||||||
|
writer.print("\"{s}\"", .{@field(self.ptr, field.name)}) catch return 0;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
writer.print("<unknown type {s}>", .{@typeName(field.type)}) catch return 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.print(" }}" ++ "\x00", .{}) catch return 0;
|
||||||
|
state.pop(1);
|
||||||
|
state.pushString(Buffer.items) catch return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn luaIndex(state: lua.LuaState) i32 {
|
||||||
|
if (state.toUserdata(@This(), 1)) |self| {
|
||||||
|
if (state.isString(2)) {
|
||||||
|
_ = self;
|
||||||
|
const argument = state.toString(2);
|
||||||
|
// core.engine_log(@typeName(@This()) ++ " got indexed. 0x{x}", .{self.handle.index});
|
||||||
|
|
||||||
|
// check metatable
|
||||||
|
if (state.getMetafield(1, @ptrCast(argument))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeTypeTable() lua.LibSpec {
|
||||||
|
const methods = blk: {
|
||||||
|
comptime var m: lua.LibSpec = &.{};
|
||||||
|
m = m ++ .{.{ .name = "__index", .func = lua.CWrap(luaIndex) }};
|
||||||
|
m = m ++ .{.{ .name = "__tostring", .func = lua.CWrap(luaToString) }};
|
||||||
|
|
||||||
|
inline for (T.ScriptExports) |name| {
|
||||||
|
m = m ++ .{.{ .name = @as([*c]const u8, @ptrCast(name)), .func = ComponentFuncWrapper(@field(T, name), T) }};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk m ++ .{.{ .name = null, .func = null }};
|
||||||
|
};
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerType(state: *lua.LuaState) !void {
|
||||||
|
core.engine_log("creating lua metatable {s}", .{MetatableName});
|
||||||
|
|
||||||
|
const methods = comptime makeTypeTable();
|
||||||
|
|
||||||
|
try state.newMetatable(@ptrCast(MetatableName));
|
||||||
|
try state.setFuncs(methods, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ComponentFuncWrapper(comptime baseFunc: anytype, comptime baseType: type) lua.LuaCFunc {
|
||||||
|
return lua.CWrap(FuncWrapper(baseFunc, baseType).wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn FuncWrapper(comptime baseFunc: anytype, comptime baseType: type) type {
|
||||||
|
return struct {
|
||||||
|
pub fn wrapper(state: lua.LuaState) i32 {
|
||||||
|
const Args = std.meta.ArgsTuple(@TypeOf(baseFunc));
|
||||||
|
|
||||||
|
var args: Args = undefined;
|
||||||
|
inline for (std.meta.fields(Args), 0..) |field, index| {
|
||||||
|
// std.debug.print("typename = {s}\n", .{@typeName(field.type)});
|
||||||
|
switch (field.type) {
|
||||||
|
f32 => {
|
||||||
|
args[index] = @as(f32, @floatCast(state.toNumber(index + 1)));
|
||||||
|
},
|
||||||
|
f64 => {
|
||||||
|
args[index] = state.toNumber(index + 1);
|
||||||
|
},
|
||||||
|
i32 => {
|
||||||
|
args[index] = @intFromFloat(state.toNumber(index + 1));
|
||||||
|
},
|
||||||
|
[]const u8 => {
|
||||||
|
args[index] = state.toString(index + 1);
|
||||||
|
},
|
||||||
|
// I'll be honest how the hell does this work?
|
||||||
|
//
|
||||||
|
// the pointer being passed in here isn't the actual resulting type...
|
||||||
|
// it's the reference type
|
||||||
|
*baseType => {
|
||||||
|
const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?;
|
||||||
|
ref.resolve();
|
||||||
|
args[index] = ref.ptr;
|
||||||
|
},
|
||||||
|
baseType => {
|
||||||
|
const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?;
|
||||||
|
ref.resolve();
|
||||||
|
args[index] = ref.ptr.*;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// if(isComponentType(field.type)) {
|
||||||
|
// const ref = state.toUserdata(field.type, index + 1).?;
|
||||||
|
// ref.resolve();
|
||||||
|
// args[index] = ref.ptr.*;
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
lua.debugPrints(true);
|
||||||
|
args[index] = (state.toUserdata(field.type, index + 1) orelse {
|
||||||
|
std.debug.print("argument error in index: {d}\n", .{index});
|
||||||
|
state.emitError("something's weird with this argument\n");
|
||||||
|
@panic("lmao");
|
||||||
|
}).*;
|
||||||
|
lua.debugPrints(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.pop(@intCast(args.len));
|
||||||
|
|
||||||
|
//const rv = @call(.always_inline, baseFunc, args);
|
||||||
|
const rv = @call(.auto, baseFunc, args);
|
||||||
|
switch (@TypeOf(rv)) {
|
||||||
|
i32, u32, i64, u64 => {
|
||||||
|
state.pushNumber(@floatFromInt(rv));
|
||||||
|
},
|
||||||
|
f32, f64 => {
|
||||||
|
state.pushNumber(@floatCast(rv));
|
||||||
|
},
|
||||||
|
void => {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
const ud = state.newZigUserdata(@TypeOf(rv)) catch @panic("not implemented");
|
||||||
|
ud.* = rv;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var Buffer: std.ArrayList(u8) = undefined;
|
||||||
|
|
||||||
|
pub fn setupFormatBuffer(allocator: std.mem.Allocator) !void {
|
||||||
|
Buffer = std.ArrayList(u8).init(allocator);
|
||||||
|
}
|
||||||
|
pub fn shutdownFormatBuffer() void {
|
||||||
|
Buffer.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("../core.zig");
|
||||||
|
const ecs = @import("../ecs.zig");
|
||||||
|
const lua = @import("lua");
|
||||||
|
const pod = lua.pod;
|
||||||
|
const scene = @import("../scene.zig");
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
ref: ecs.EcsContainerRef = undefined,
|
||||||
|
name: []const u8 = undefined,
|
||||||
|
luaNew: *const fn (state: lua.LuaState, core.ObjectHandle, ?*anyopaque) void = undefined,
|
||||||
|
|
||||||
|
pub const PodDataTable: pod.DataTable = .{
|
||||||
|
.name = "ComponentRegistration",
|
||||||
|
.banInstantiation = true,
|
||||||
|
.toStringOverride = lua.CWrap(toString),
|
||||||
|
};
|
||||||
|
|
||||||
|
// I should really move all the registration to this file.
|
||||||
|
pub fn toString(state: lua.LuaState) i32 {
|
||||||
|
var workBuffer: [256]u8 = undefined;
|
||||||
|
const ud = state.toUserdata(@This(), 1).?;
|
||||||
|
state.pop(1);
|
||||||
|
const str = std.fmt.bufPrintZ(&workBuffer, "Component Data Table: {s} 0x{x}", .{ ud.name, ud.ref.ptr }) catch return 0;
|
||||||
|
state.pushString(str) catch return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createComponent(self: @This(), handle: core.ObjectHandle) ?*anyopaque {
|
||||||
|
const ref = self.ref;
|
||||||
|
const rv = ref.vtable.createWithHandle(ref.ptr, handle);
|
||||||
|
// std.debug.print("createComponent: {p}\n", .{rv});
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lua = @import("lua");
|
||||||
|
const ecs = @import("../ecs.zig");
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("../core.zig");
|
||||||
|
const pod = lua.pod;
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
const luaRegLibs: []const lua.c.luaL_Reg = &.{
|
||||||
|
.{ .name = "registerTick", .func = lua.CWrap(registerTick) },
|
||||||
|
.{ .name = null, .func = null },
|
||||||
|
};
|
||||||
|
|
||||||
|
// register core types and subsystems into the scripting engine
|
||||||
|
pub fn registerTypes() !void {
|
||||||
|
// transform POD type
|
||||||
|
|
||||||
|
const state = script.getState();
|
||||||
|
try lua.pod.registerPodType(state, core.Vector);
|
||||||
|
try lua.pod.registerPodType(state, core.Vectorf);
|
||||||
|
try lua.pod.registerPodType(state, core.Vector2);
|
||||||
|
try lua.pod.registerPodType(state, core.Vector2f);
|
||||||
|
// try lua.pod.registerPodType(state, core.Transform);
|
||||||
|
|
||||||
|
// lua.pod.registerPodType(state, core.Vector4, "Vector4");
|
||||||
|
try state.createLibrary("Systems", luaRegLibs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerTick(l: lua.LuaState) i32 {
|
||||||
|
// two arguments first one is going to be userdata entity
|
||||||
|
// second one is going to be a lua function.
|
||||||
|
_ = l;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ScriptTicks = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), deltaTime: f64) void {
|
||||||
|
_ = self;
|
||||||
|
const state = script.getState();
|
||||||
|
_ = state.getGlobal("__TickScripts");
|
||||||
|
state.pushNumber(deltaTime);
|
||||||
|
state.pcallStack(1) catch {
|
||||||
|
core.engine_logs("could not execute lua script");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const lua = @import("lua");
|
||||||
|
const std = @import("std");
|
||||||
|
const script = @import("script.zig");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
const BumpArena = core.BumpArena;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub fn hashStackList(stack: []const usize) u32 {
|
||||||
|
var c: u32 = 0;
|
||||||
|
for (stack) |l| {
|
||||||
|
c = combineHash32(c, hashPtr(l));
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn combineHash32(lhs: u32, rhs: u32) u32 {
|
||||||
|
return lhs ^ (rhs +% 0x9e3779b9 +% (lhs << 6) +% (lhs >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn hashPtr(value: usize) u32 {
|
||||||
|
var k = value +% 1;
|
||||||
|
|
||||||
|
if (@sizeOf(@TypeOf(value)) == 8) {
|
||||||
|
k = (~k) +% (k << 18);
|
||||||
|
k = k ^ std.math.rotr(usize, k, 31);
|
||||||
|
k = @mulWithOverflow(k, 21)[0];
|
||||||
|
k = k ^ std.math.rotr(usize, k, 11);
|
||||||
|
k = k +% (k << 6);
|
||||||
|
k = k ^ std.math.rotr(usize, k, 22);
|
||||||
|
return @truncate(k);
|
||||||
|
} else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "1 million stacks." {
|
||||||
|
// see how fast i can walk 1 million stacks
|
||||||
|
var timer = std.time.Timer.start() catch unreachable;
|
||||||
|
const startTime = timer.read();
|
||||||
|
for (0..100000) |_| {
|
||||||
|
core.stacks.pushCallStack();
|
||||||
|
}
|
||||||
|
const endTime = timer.read();
|
||||||
|
std.debug.print("timeElapsed 100k callstacks {d}s", .{@as(f64, @floatFromInt(endTime - startTime)) / 1000000000});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "test hash64s" {
|
||||||
|
const pointers: []const usize = &.{
|
||||||
|
0x0,
|
||||||
|
0x1,
|
||||||
|
0xffffffff1eed1234,
|
||||||
|
0xffffffff_ffffffff,
|
||||||
|
0xffffffff_ffffffff - 1,
|
||||||
|
};
|
||||||
|
var combined: u32 = 0;
|
||||||
|
|
||||||
|
for (pointers) |l| {
|
||||||
|
const h = hashPtr(l);
|
||||||
|
std.debug.print("l = {x}, hash = {x}\n", .{ l, h });
|
||||||
|
combined = combineHash32(combined, h);
|
||||||
|
}
|
||||||
|
std.debug.print("combined hash = {x} ({x})\n", .{ combined, hashStackList(pointers) });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const StackCompactor = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
stackArena: BumpArena,
|
||||||
|
stackMap: std.AutoHashMapUnmanaged(u32, *CallStack) = .{},
|
||||||
|
const CallStack = struct {
|
||||||
|
pointers: []usize,
|
||||||
|
debugStr: []?[]u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.stackArena = try BumpArena.init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addNewCallstack(self: *@This(), stack: []const usize) !u32 {
|
||||||
|
const hash = hashStackList(stack);
|
||||||
|
const bumpAllocator = self.stackArena.allocator();
|
||||||
|
|
||||||
|
if (!self.stackMap.contains(hash)) {
|
||||||
|
const ownedStack: *CallStack = try bumpAllocator.create(CallStack);
|
||||||
|
ownedStack.pointers = try bumpAllocator.dupe(usize, stack);
|
||||||
|
ownedStack.debugStr = try bumpAllocator.alloc(?[]u8, stack.len);
|
||||||
|
|
||||||
|
const debug_info = std.debug.getSelfDebugInfo() catch {
|
||||||
|
try self.stackMap.put(self.allocator, hash, ownedStack);
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (stack, 0..) |address, i| {
|
||||||
|
ownedStack.debugStr[i] = null;
|
||||||
|
|
||||||
|
const module = debug_info.getModuleForAddress(address) catch continue;
|
||||||
|
const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch continue;
|
||||||
|
|
||||||
|
if (symbol_info.line_info) |line_info| {
|
||||||
|
ownedStack.debugStr[i] = try std.fmt.allocPrintZ(
|
||||||
|
bumpAllocator,
|
||||||
|
"{d}> @0x{x} symbol_name: {s} {s} > {s}: {d}",
|
||||||
|
.{ i, address, symbol_info.symbol_name, symbol_info.compile_unit_name, line_info.file_name, line_info.line },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ownedStack.debugStr[i] = try std.fmt.allocPrintZ(
|
||||||
|
bumpAllocator,
|
||||||
|
"{d}> @0x{x} symbol_name: {s} {s} > no file info",
|
||||||
|
.{ i, address, symbol_info.symbol_name, symbol_info.compile_unit_name },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.stackMap.put(self.allocator, hash, ownedStack);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: @This()) void {
|
||||||
|
self.stackList.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCallStack(self: *@This()) u32 {
|
||||||
|
if (builtin.os.tag != .windows) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var context: std.debug.ThreadContext = undefined;
|
||||||
|
const has_context = std.debug.getContext(&context);
|
||||||
|
|
||||||
|
if (!has_context) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr_buf: [1024]usize = undefined;
|
||||||
|
const n = std.debug.walkStackWindows(addr_buf[0..], &context);
|
||||||
|
|
||||||
|
return self.addNewCallstack(addr_buf[0..n]) catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var stackCompactor: *core.StackCompactor = undefined;
|
||||||
|
|
||||||
|
pub fn initStackCompactor() void {
|
||||||
|
stackCompactor = core.StackCompactor.create(std.heap.c_allocator) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn pushCallStack() void {
|
||||||
|
var context: std.debug.ThreadContext = undefined;
|
||||||
|
const has_context = std.debug.getContext(&context);
|
||||||
|
|
||||||
|
if (!has_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr_buf: [1024]usize = undefined;
|
||||||
|
const n = std.debug.walkStackWindows(addr_buf[0..], &context);
|
||||||
|
|
||||||
|
stackCompactor.addNewCallstack(addr_buf[0..n]) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getStackCompactor() *StackCompactor {
|
||||||
|
return stackCompactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn walkAndPrintStack() void {
|
||||||
|
var context: std.debug.ThreadContext = undefined;
|
||||||
|
const has_context = std.debug.getContext(&context);
|
||||||
|
|
||||||
|
if (!has_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr_buf: [1024]usize = undefined;
|
||||||
|
const n = std.debug.walkStackWindows(addr_buf[0..], &context);
|
||||||
|
|
||||||
|
const debug_info = std.debug.getSelfDebugInfo() catch {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < n) : (i += 1) {
|
||||||
|
const address = addr_buf[i];
|
||||||
|
|
||||||
|
const module = debug_info.getModuleForAddress(address) catch continue;
|
||||||
|
const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch continue;
|
||||||
|
|
||||||
|
std.debug.print("@0x{x} symbol_name: {s} {s} > ", .{ address, symbol_info.symbol_name, symbol_info.compile_unit_name });
|
||||||
|
|
||||||
|
if (symbol_info.line_info) |line_info| {
|
||||||
|
std.debug.print(" {s}:{d}\n", .{ line_info.file_name, line_info.line });
|
||||||
|
} else {
|
||||||
|
std.debug.print(" no file info\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn dupeZ(comptime T: type, allocator: std.mem.Allocator, source: []const T) ![]T {
|
||||||
|
var buff: []T = try allocator.alloc(T, source.len + 1);
|
||||||
|
for (source, 0..source.len) |s, i| {
|
||||||
|
buff[i] = s;
|
||||||
|
}
|
||||||
|
buff[source.len] = 0;
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dupe(comptime T: type, allocator: std.mem.Allocator, source: []const T) ![]T {
|
||||||
|
var buff: []T = try allocator.alloc(T, source.len);
|
||||||
|
for (source, 0..) |s, i| {
|
||||||
|
buff[i] = s;
|
||||||
|
}
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn implement_func_for_tagged_union_nonull(
|
||||||
|
self: anytype,
|
||||||
|
comptime funcName: []const u8,
|
||||||
|
comptime returnType: type,
|
||||||
|
args: anytype,
|
||||||
|
) returnType {
|
||||||
|
const Self = @TypeOf(self);
|
||||||
|
inline for (@typeInfo(std.meta.Tag(Self)).Enum.fields) |field| {
|
||||||
|
if (@as(std.meta.Tag(Self), @enumFromInt(field.value)) == self) {
|
||||||
|
if (@hasDecl(@TypeOf(@field(self, field.name)), funcName)) {
|
||||||
|
return @field(@field(self, field.name), funcName)(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
const core = @import("core");
|
||||||
|
const std = @import("std");
|
||||||
|
const memory = core.MemoryTracker;
|
||||||
|
|
||||||
|
const engine_log = core.engine_log;
|
||||||
|
const engine_logs = core.engine_logs;
|
||||||
|
|
||||||
|
test "simple systems setup for core" {
|
||||||
|
std.testing.refAllDecls(core.algorithm);
|
||||||
|
|
||||||
|
memory.MTSetup(std.testing.allocator, .{ .timeline = false });
|
||||||
|
defer memory.MTShutdown();
|
||||||
|
const tracker = memory.MTGet().?;
|
||||||
|
const allocator = tracker.allocator();
|
||||||
|
|
||||||
|
std.debug.print("Starting up \n", .{});
|
||||||
|
engine_logs("systems starting");
|
||||||
|
try core.start_module(.{}, .{ .unitTest = true }, allocator);
|
||||||
|
defer core.shutdown_module(allocator);
|
||||||
|
|
||||||
|
engine_logs("systems started, shutting down");
|
||||||
|
memory.MTPrintStatsDelta();
|
||||||
|
|
||||||
|
try memory.dumpTimeline("test-core-timeline.txt");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// first person camera...
|
||||||
|
// god... modularity with user inputs are fucking crazy man...
|
||||||
|
//
|
||||||
|
// I like the idea of an input stack.
|
||||||
|
//
|
||||||
|
// inputs go through a list of stacked listeners.
|
||||||
|
//
|
||||||
|
// listeners can choose to either consume or pass a given input event.
|
||||||
|
// but listeners always listen.
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const SpirvReflect = @import("SpirvReflect");
|
||||||
|
|
||||||
|
const dependencyList = [_][]const u8{
|
||||||
|
"vulkan",
|
||||||
|
"vma",
|
||||||
|
"glfw3",
|
||||||
|
"core",
|
||||||
|
"assets",
|
||||||
|
"platform",
|
||||||
|
"objLoader",
|
||||||
|
"ozz",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const mod = b.addModule("graphics", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
.root_source_file = b.path("src/graphics.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
mod.addAnonymousImport("texture_sample.png", .{ .root_source_file = b.path("defaults/texture_sample.png") });
|
||||||
|
|
||||||
|
const options = b.addOptions();
|
||||||
|
options.addOption(bool, "force_mailbox", b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false);
|
||||||
|
|
||||||
|
mod.addOptions("game_build_opts", options);
|
||||||
|
|
||||||
|
for (dependencyList) |depName| {
|
||||||
|
const dep = b.dependency(depName, .{ .target = target, .optimize = optimize });
|
||||||
|
mod.addImport(depName, dep.module(depName));
|
||||||
|
if (std.mem.eql(u8, depName, "ozz")) {
|
||||||
|
mod.linkLibrary(dep.artifact("ozz_cpp"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const spirvGen = SpirvReflect.SpirvGenerator2.init(b, .{ .optimize = optimize });
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/triangle_mesh.vert"), "triangle_mesh_vert");
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/default_lit.frag"), "default_lit");
|
||||||
|
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/skybox/skybox.vert"), "skybox_vert");
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/skybox/skybox.frag"), "skybox_frag");
|
||||||
|
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/debug.vert"), "debug_vert");
|
||||||
|
spirvGen.addShader(mod, b.path("shaders/debug.frag"), "debug_frag");
|
||||||
|
|
||||||
|
// === simple little integration test ===
|
||||||
|
//
|
||||||
|
// this doesn't really do anything other than call a few functions
|
||||||
|
// to make sure that we properly linked everything
|
||||||
|
const test_step = b.step("test", "");
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("tests/tests.zig"),
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.root_module.addImport("graphics", mod);
|
||||||
|
|
||||||
|
for (dependencyList) |depName| {
|
||||||
|
const dep = b.dependency(depName, .{ .target = target, .optimize = optimize });
|
||||||
|
tests.root_module.addImport(depName, dep.module(depName));
|
||||||
|
}
|
||||||
|
const runArtifact = b.addRunArtifact(tests);
|
||||||
|
test_step.dependOn(&runArtifact.step);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
.{
|
||||||
|
.name = "graphics",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
//
|
||||||
|
.vulkan = .{ .path = "../../lib/vulkan" },
|
||||||
|
.vma = .{ .path = "../../lib/vma" },
|
||||||
|
.glfw3 = .{ .path = "../../lib/glfw3" },
|
||||||
|
.cgltf = .{ .path = "../../lib/cgltf" },
|
||||||
|
.objLoader = .{ .path = "../../lib/objLoader" },
|
||||||
|
.SpirvReflect = .{ .path = "../../lib/spirv-reflect-zig" },
|
||||||
|
.ozz = .{ .path = "../../lib/ozz" },
|
||||||
|
// core
|
||||||
|
.core = .{ .path = "../core" },
|
||||||
|
.assets = .{ .path = "../assets" },
|
||||||
|
.platform = .{ .path = "../platform" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -0,0 +1,32 @@
|
||||||
|
//glsl version 4.5
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 in_color;
|
||||||
|
layout (location = 1) in vec2 texCoord;
|
||||||
|
layout (location = 2) in vec3 worldPosition;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform CameraBuffer{
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
mat4 viewproj;
|
||||||
|
vec4 position;
|
||||||
|
} cameraData;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 1) uniform SceneData{
|
||||||
|
vec4 fogColor; // w is for exponent
|
||||||
|
vec4 fogDistances; //x for min, y for max, zw unused.
|
||||||
|
vec4 ambientColor;
|
||||||
|
vec4 sunlightDirection; //w for sun power
|
||||||
|
vec4 sunlightColor;
|
||||||
|
} sceneData;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 color = in_color.rgb;
|
||||||
|
float cameraDist = length(cameraData.position.xyz - worldPosition);
|
||||||
|
float opacity = (1.0) - (cameraDist / 300);
|
||||||
|
outFragColor = vec4(color, opacity * 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 vPosition;
|
||||||
|
layout (location = 1) in vec3 vNormal;
|
||||||
|
layout (location = 2) in vec4 vColor;
|
||||||
|
layout (location = 3) in vec2 vTexCoord;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
layout (location = 1) out vec2 texCoord;
|
||||||
|
layout (location = 2) out vec3 worldPosition;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform CameraBuffer{
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
mat4 viewproj;
|
||||||
|
vec4 position;
|
||||||
|
} cameraData;
|
||||||
|
|
||||||
|
// size: 16 x 4 + 3 x 4 = 76 => 128 bytes per object per alignment
|
||||||
|
struct ObjectData {
|
||||||
|
mat4 model;
|
||||||
|
vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std140, set = 1, binding = 0) readonly buffer ObjectBuffer{
|
||||||
|
ObjectData objects[];
|
||||||
|
} objectBuffer;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
ObjectData object = objectBuffer.objects[gl_BaseInstance];
|
||||||
|
mat4 modelMatrix = object.model;
|
||||||
|
mat4 final = (cameraData.viewproj * modelMatrix);
|
||||||
|
vec4 position = final * vec4(vPosition, 1.0f);
|
||||||
|
gl_Position = position;
|
||||||
|
outColor = object.color.xyz;
|
||||||
|
texCoord = vTexCoord;
|
||||||
|
vec4 modelPos = modelMatrix * vec4(vPosition, 1.0f);
|
||||||
|
worldPosition = modelPos.xyz;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
//glsl version 4.5
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
#extension GL_EXT_nonuniform_qualifier : require
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 in_color;
|
||||||
|
layout (location = 1) in vec2 texCoord;
|
||||||
|
layout (location = 2) in vec3 worldPosition;
|
||||||
|
layout (location = 3) flat in uint textureId;
|
||||||
|
layout (location = 4) flat in uint baseInstance;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
|
||||||
|
#include "globalSet.glsl"
|
||||||
|
|
||||||
|
#include "sharedSsbo.glsl"
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// outFragColor = vec4(in_color + 0.25 * sceneData.ambientColor.xyz,1.0f);
|
||||||
|
// outFragColor = vec4(texCoord.x, texCoord.y, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
// vec4 color = texture(tex1, texCoord).xyzw;
|
||||||
|
vec4 color = texture(gTex[textureId], texCoord).xyzw;
|
||||||
|
|
||||||
|
if(color.w < 0.05f)
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
float cameraDist = length(cameraData.position.xyz - worldPosition);
|
||||||
|
float opacity = clamp((1.f) - (clamp(cameraDist - 300, 0, 300) / 300.f), 0.f, 1.f);
|
||||||
|
|
||||||
|
//float opacity = 1.0;
|
||||||
|
if(opacity < 0.05f)
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
// outFragColor = vec4(mix(sceneData.fogColor.xyz, color.xyz, opacity), color.w);
|
||||||
|
outFragColor = vec4(color.xyz, opacity);
|
||||||
|
|
||||||
|
//vec3 mixed = mix(normalize(vec3(0.5, 0.3, 0.2)) * 3, vec3(0.2, 0.2, 1) * 3, texCoord.y * 2);
|
||||||
|
//outFragColor = vec4(color.xyz, 1.0f);
|
||||||
|
//outFragColor = vec4(0.0, 1.0, 0.0, 1.0f);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
layout (set = 0, binding = 0) uniform CameraBuffer{
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
mat4 viewproj;
|
||||||
|
mat4 viewprojAlt;
|
||||||
|
vec4 position;
|
||||||
|
} cameraData;
|
||||||
|
|
||||||
|
|
||||||
|
layout(set = 0, binding = 1) uniform SceneData{
|
||||||
|
vec4 fogColor; // w is for exponent
|
||||||
|
vec4 fogDistances; //x for min, y for max, zw unused.
|
||||||
|
vec4 ambientColor;
|
||||||
|
vec4 sunlightDirection; //w for sun power
|
||||||
|
vec4 sunlightColor;
|
||||||
|
} sceneData;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 2) uniform sampler2D[] gTex;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
struct ObjectData {
|
||||||
|
mat4 model;
|
||||||
|
uint textureId;
|
||||||
|
int animation; // this is the index offset of the first matrix in the animation finals buffer.
|
||||||
|
uint flags0;
|
||||||
|
// packed flags0 flags;
|
||||||
|
// [0,0]: alwaysInFront
|
||||||
|
// [1,1]: useAltCamera
|
||||||
|
uint pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std140, set = 1, binding = 0) readonly buffer ObjectBuffer{
|
||||||
|
ObjectData objects[];
|
||||||
|
} objectBuffer;
|
||||||
|
|
||||||
|
|
||||||
|
uint flag0_AlwaysInFront(uint flags)
|
||||||
|
{
|
||||||
|
return flags & 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint flag0_useAltFov(uint flags)
|
||||||
|
{
|
||||||
|
return (flags >> 1) & 0x1;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
layout(std140, set = 1, binding = 1) readonly buffer BoneBuffer{
|
||||||
|
mat4 finals[];
|
||||||
|
} animationBuffer;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
#include "../globalSet.glsl"
|
||||||
|
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform samplerCube cubemap;
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 inUVW;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outFragColor = texture(cubemap, inUVW);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
#include "../vertexInput.glsl"
|
||||||
|
#include "../globalSet.glsl"
|
||||||
|
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outUVW;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outUVW = vPosition;
|
||||||
|
// Convert cubemap coordinates into Vulkan coordinate space
|
||||||
|
|
||||||
|
// Remove translation from view matrix
|
||||||
|
mat4 viewMat = mat4(mat3(cameraData.view));
|
||||||
|
gl_Position = cameraData.proj * viewMat * vec4(vPosition.xyz, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#version 460
|
||||||
|
|
||||||
|
|
||||||
|
#include "vertexInput.glsl"
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
layout (location = 1) out vec2 texCoord;
|
||||||
|
layout (location = 2) out vec3 worldPosition;
|
||||||
|
layout (location = 3) flat out uint textureId;
|
||||||
|
layout (location = 4) flat out uint baseInstance;
|
||||||
|
|
||||||
|
#include "globalSet.glsl"
|
||||||
|
#include "sharedSsbo.glsl"
|
||||||
|
#include "skeletalBuffers.glsl"
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 vertexPos = vec3(0.0);
|
||||||
|
int animation = objectBuffer.objects[gl_BaseInstance].animation;
|
||||||
|
|
||||||
|
if(animation == -1)
|
||||||
|
{
|
||||||
|
vertexPos = vPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint animation = objectBuffer.objects[gl_BaseInstance].animation;
|
||||||
|
|
||||||
|
for(int i = 0; i < 4; i += 1)
|
||||||
|
{
|
||||||
|
uint boneIndex = bones[i];
|
||||||
|
float weight = float(weights[i]) / 255;
|
||||||
|
|
||||||
|
// this will depend on ozz's finals format
|
||||||
|
mat4 boneTransform = animationBuffer.finals[animation + boneIndex];
|
||||||
|
vertexPos += weight * ( boneTransform * vec4(vPosition, 1.0) ).xyz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 modelMatrix = objectBuffer.objects[gl_BaseInstance].model;
|
||||||
|
|
||||||
|
mat4 final;
|
||||||
|
|
||||||
|
if(flag0_useAltFov(objectBuffer.objects[gl_BaseInstance].flags0) == 1)
|
||||||
|
{
|
||||||
|
final = (cameraData.viewprojAlt * modelMatrix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final = (cameraData.viewproj * modelMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 position = final * vec4(vertexPos, 1.0f);
|
||||||
|
|
||||||
|
if( flag0_AlwaysInFront(objectBuffer.objects[gl_BaseInstance].flags0) == 1)
|
||||||
|
{
|
||||||
|
position.z *= 0.0001;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseInstance = gl_BaseInstance;
|
||||||
|
gl_Position = position;
|
||||||
|
textureId = objectBuffer.objects[gl_BaseInstance].textureId;
|
||||||
|
outColor = vec3(vColor.x, vColor.y, vColor.z);
|
||||||
|
texCoord = vTexCoord;
|
||||||
|
worldPosition = (modelMatrix * vec4(0,0,0,1)).xyz;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : enable
|
||||||
|
layout (location = 0) in vec3 vPosition;
|
||||||
|
layout (location = 1) in vec3 vNormal;
|
||||||
|
layout (location = 2) in vec4 vColor;
|
||||||
|
layout (location = 3) in vec2 vTexCoord;
|
||||||
|
layout (location = 4) in u8vec4 bones;
|
||||||
|
layout (location = 5) in u8vec4 weights;
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
pixels: []u8,
|
||||||
|
extent: core.Vector2i,
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const colors = core.colors;
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, extent: core.Vector2i) !@This() {
|
||||||
|
return .{
|
||||||
|
.pixels = try allocator.alignedAlloc(u8, 8, @intCast(extent.x * extent.y * 4)),
|
||||||
|
.extent = extent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *@This(), clearColor: colors.ColorRGBA8) void {
|
||||||
|
var as32: []u32 = undefined;
|
||||||
|
as32.len = self.pixels.len / 4;
|
||||||
|
as32.ptr = @alignCast(@ptrCast(self.pixels.ptr));
|
||||||
|
|
||||||
|
@memset(as32, @as(u32, @bitCast(clearColor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn getPixel(self: *@This(), position: core.Vector2i) *colors.ColorRGBA8 {
|
||||||
|
const offset = position.x * position.y * 4;
|
||||||
|
const r: *u8 = &self.pixels[@intCast(offset)];
|
||||||
|
|
||||||
|
return @as(*colors.ColorRGBA8, @alignCast(@ptrCast(r)));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
pub const AnimResolverRef = core.Reference(AnimResolverInterface);
|
||||||
|
pub const AnimResolverInterface = core.MakeInterface("AnimResolverVTable", struct {
|
||||||
|
// this tick function should evaluate the current state of the resolver
|
||||||
|
// and then update the animator's finals[] matrix list.
|
||||||
|
resolve: *const fn (*anyopaque, f64, *Animator) void,
|
||||||
|
onSkeletonSet: ?*const fn (*anyopaque, *Animator) void = null,
|
||||||
|
|
||||||
|
create: *const fn (std.mem.Allocator) core.EngineDataEventError!*anyopaque,
|
||||||
|
destroy: *const fn (*anyopaque) void,
|
||||||
|
|
||||||
|
pub fn Implement(comptime TargetType: type) @This() {
|
||||||
|
const Wrap = struct {
|
||||||
|
pub fn create(allocator: std.mem.Allocator) core.EngineDataEventError!*anyopaque {
|
||||||
|
const new = TargetType.create(allocator) catch return core.EngineDataEventError.BadInit;
|
||||||
|
return @ptrCast(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(p: *anyopaque) void {
|
||||||
|
const ptr: *TargetType = @ptrCast(@alignCast(p));
|
||||||
|
ptr.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onSkeletonSet(p: *anyopaque, a: *Animator) void {
|
||||||
|
const ptr: *TargetType = @ptrCast(@alignCast(p));
|
||||||
|
ptr.onSkeletonSet(a) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(p: *anyopaque, dt: f64, a: *Animator) void {
|
||||||
|
const ptr: *TargetType = @ptrCast(@alignCast(p));
|
||||||
|
ptr.resolve(dt, a) catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return .{
|
||||||
|
.destroy = Wrap.destroy,
|
||||||
|
.create = Wrap.create,
|
||||||
|
.resolve = Wrap.resolve,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const AnimSampler = struct {
|
||||||
|
name: ?core.Name = null,
|
||||||
|
track: ?*AnimationTrack = null,
|
||||||
|
playbackRate: f32 = 1.0,
|
||||||
|
time: f32 = 0.0,
|
||||||
|
outputLocals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{},
|
||||||
|
|
||||||
|
// other features
|
||||||
|
// paused: bool = false,
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.outputLocals.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOutput(self: *@This()) []ozz.SoaTransform {
|
||||||
|
return self.outputLocals.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sampleAndAdvance(self: *@This(), allocator: std.mem.Allocator, dt: f64, animator: *Animator) void {
|
||||||
|
self.sample(allocator, animator);
|
||||||
|
self.advance(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(self: *@This(), dt: f64) void {
|
||||||
|
if (self.track == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatHelpers.updateTrackTime(&self.time, dt, self.playbackRate, self.track.?.endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setName(self: *@This(), name: core.Name) void {
|
||||||
|
self.name = name;
|
||||||
|
self.track = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(self: *@This(), allocator: std.mem.Allocator, animator: *Animator) void {
|
||||||
|
if (self.name == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.track == null) {
|
||||||
|
self.track = animation_system.gAnimationSys.animTracks.get(self.name.?.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.outputLocals.resize(allocator, animator.jointLength) catch return;
|
||||||
|
|
||||||
|
if (self.track) |track| {
|
||||||
|
if (track.endTime < 0.01) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.sampleAnimation(self.time, track, self.outputLocals.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BlenderList = struct {
|
||||||
|
backing: std.mem.Allocator,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
jobLayers: std.ArrayListUnmanaged(ozz.Layer) = .{},
|
||||||
|
jobLayersAdditive: std.ArrayListUnmanaged(ozz.Layer) = .{},
|
||||||
|
useAdditive: bool = false,
|
||||||
|
|
||||||
|
threshold: f32 = 0.01,
|
||||||
|
jointLength: usize = 0,
|
||||||
|
blendingJob: ozz.BlendingJob = .{},
|
||||||
|
|
||||||
|
pub fn create(backingAllocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try backingAllocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.backing = backingAllocator,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(backingAllocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.backing.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateRestPose(self: *@This(), animator: *Animator) !void {
|
||||||
|
if (animator.skeleton) |skeleton| {
|
||||||
|
self.jointLength = skeleton.sk.numJoints();
|
||||||
|
self.blendingJob.rest_pose = skeleton.sk.getRestPoseModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearLayers(self: *@This()) void {
|
||||||
|
self.jobLayersAdditive.clearRetainingCapacity();
|
||||||
|
self.jobLayers.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addLayer(self: *@This(), transform: []ozz.SoaTransform, weight: f32, settings: anytype) void {
|
||||||
|
const layer = self.jobLayers.addOne(self.arena.allocator()) catch unreachable;
|
||||||
|
layer.* = .{
|
||||||
|
.weight = weight,
|
||||||
|
.transform = ozz.makeSpan(transform),
|
||||||
|
};
|
||||||
|
_ = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateAndRun(self: *@This(), output: []ozz.SoaTransform) void {
|
||||||
|
self.updateBlendingJob();
|
||||||
|
self.runBlendingJob(output) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateBlendingJob(self: *@This()) void {
|
||||||
|
self.blendingJob.threshold = self.threshold;
|
||||||
|
self.blendingJob.layers = ozz.makeSpan(self.jobLayers.items);
|
||||||
|
//self.blendingJob.additive_layers = if (self.useAdditive) ozz.makeSpan(self.jobLayersAdditive.items) else .{};
|
||||||
|
self.blendingJob.additive_layers = .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runBlendingJob(self: *@This(), output: []ozz.SoaTransform) !void {
|
||||||
|
self.blendingJob.output = ozz.makeSpan(output);
|
||||||
|
if (!self.blendingJob.run()) {
|
||||||
|
core.engine_logs("blending job failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// resolver helpers
|
||||||
|
pub const FloatHelpers = struct {
|
||||||
|
pub inline fn updateTrackTime(target: *f32, dt: f64, rate: f32, endTime: f32) void {
|
||||||
|
target.* += @as(f32, @floatCast(dt)) * rate;
|
||||||
|
while (target.* > endTime) {
|
||||||
|
target.* -= endTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// samples a single animation, same as the default behaviour.
|
||||||
|
// used as a test for the resolver system
|
||||||
|
pub const SingleAnimationResolver = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
locals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{},
|
||||||
|
|
||||||
|
track: ?*AnimationTrack = null,
|
||||||
|
playback: f32 = 0.0,
|
||||||
|
playbackRate: f32 = 1.0,
|
||||||
|
|
||||||
|
pub const AnimResolverVTable = AnimResolverInterface.Implement(@This());
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onSkeletonSet(self: *@This(), animator: *Animator) !void {
|
||||||
|
if (animator.skeleton) |skeleton| {
|
||||||
|
try self.locals.resize(self.allocator, skeleton.sk.numSoaJoints());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(self: *@This(), dt: f64, animator: *Animator) !void {
|
||||||
|
self.track = animator.track;
|
||||||
|
if (self.track == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const track = self.track.?;
|
||||||
|
if (track.endTime < 0.01) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatHelpers.updateTrackTime(&self.playback, dt, self.playbackRate, track.endTime);
|
||||||
|
animator.sampleAnimation(self.playback, track, self.locals.items);
|
||||||
|
animator.commitLocalToModel(self.locals.items);
|
||||||
|
animator.modelToFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.locals.deinit(self.allocator);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const animation_system = @import("animationSystem.zig");
|
||||||
|
const Animator = animation_system.Animator;
|
||||||
|
const AnimationTrack = animation_system.AnimationTrack;
|
||||||
|
|
||||||
|
const core = @import("core");
|
||||||
|
const std = @import("std");
|
||||||
|
const ozz = @import("ozz");
|
||||||
|
|
@ -0,0 +1,480 @@
|
||||||
|
// big main sy.itemsstem for animation
|
||||||
|
|
||||||
|
const ozz = @import("ozz");
|
||||||
|
const core = @import("core");
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const BoneHandle = enum(u8) { _ };
|
||||||
|
|
||||||
|
pub const Skeleton = struct {
|
||||||
|
sk: *ozz.Skeleton,
|
||||||
|
inverseBinds: std.ArrayListUnmanaged(core.Mat) = .{},
|
||||||
|
jointMapping: std.StringHashMapUnmanaged(u8) = .{},
|
||||||
|
|
||||||
|
pub fn buildJointMap(self: *@This(), allocator: std.mem.Allocator) !void {
|
||||||
|
for (self.sk.getJointsList(), 0..) |jointName, i| {
|
||||||
|
// std.debug.print("jointName {d} {s}\n", .{ i, jointName });
|
||||||
|
const str = std.mem.span(jointName);
|
||||||
|
try self.jointMapping.put(allocator, str, @intCast(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getBoneHandleByName(self: @This(), string: []const u8) ?BoneHandle {
|
||||||
|
if (self.jointMapping.get(string)) |x| {
|
||||||
|
return @enumFromInt(x);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.sk.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AnimationTrack = struct {
|
||||||
|
animation: *ozz.Animation,
|
||||||
|
endTime: f32 = 1.0,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.animation.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PlaybackTrack = struct {
|
||||||
|
track: ?*AnimationTrack = null,
|
||||||
|
playback: f32 = 0.0,
|
||||||
|
playbackRate: f32 = 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Animator = struct {
|
||||||
|
jointRemap: ?[]u8 = null,
|
||||||
|
animationName: ?core.Name = null,
|
||||||
|
skeleton: ?*Skeleton = null,
|
||||||
|
skeletonName: ?core.Name = null,
|
||||||
|
sjc: *ozz.SamplingJobContext = undefined,
|
||||||
|
|
||||||
|
track: ?*AnimationTrack = null,
|
||||||
|
playback: f32 = 0.0,
|
||||||
|
playbackRate: f32 = 1.0,
|
||||||
|
|
||||||
|
// todo.. implement blending
|
||||||
|
// animations: [4]*ozz.Animation = undefined,
|
||||||
|
// timelines: [4]f32 = .{ 0, 0, 0, 0 },
|
||||||
|
animationCount: u32 = 0,
|
||||||
|
|
||||||
|
locals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{},
|
||||||
|
models: std.ArrayListUnmanaged(ozz.Float4x4) = .{},
|
||||||
|
finals: std.ArrayListUnmanaged(core.Mat) = .{},
|
||||||
|
finalsSpan: core.Span = undefined,
|
||||||
|
|
||||||
|
entity: core.Entity = undefined,
|
||||||
|
jointLength: usize = 0,
|
||||||
|
|
||||||
|
resolverRef: ?AnimResolverRef = null,
|
||||||
|
|
||||||
|
pub var allocator: std.mem.Allocator = undefined;
|
||||||
|
// oh god if I want to support multiple animation blending...
|
||||||
|
// maybe the kernel should contain a fixed amount of animations?
|
||||||
|
|
||||||
|
pub fn initECS(self: *@This(), handle: core.SetHandle) void {
|
||||||
|
// get the mesh component
|
||||||
|
self.entity = core.Entity{ .handle = handle };
|
||||||
|
|
||||||
|
if (self.entity.get(graphics.StaticMesh)) |mesh| {
|
||||||
|
mesh.animated = true; //todo
|
||||||
|
mesh.animator = self;
|
||||||
|
self.sjc = ozz.SamplingJobContext.createMaxTracks(256);
|
||||||
|
} else {
|
||||||
|
@panic("animator added to an entity that does not have a mesh component");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getBoneTransform(self: *@This(), handle: BoneHandle) core.Mat {
|
||||||
|
return @bitCast(self.models.items[@intFromEnum(handle)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSkeletonByName(self: *@This(), skName: core.Name) !void {
|
||||||
|
if (self.skeleton != null) {
|
||||||
|
// return the previous span and allocate a new one.
|
||||||
|
gAnimationSys.slots.removeSpan(self.finalsSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.skeletonName = skName;
|
||||||
|
self.skeleton = gAnimationSys.skeletons.get(self.skeletonName.?.handle()).?;
|
||||||
|
const numJoints = self.skeleton.?.sk.numJoints();
|
||||||
|
self.jointLength = numJoints;
|
||||||
|
|
||||||
|
self.sjc.resize(@intCast(numJoints));
|
||||||
|
try self.locals.resize(allocator, self.skeleton.?.sk.numSoaJoints());
|
||||||
|
try self.models.resize(allocator, numJoints);
|
||||||
|
try self.finals.resize(allocator, numJoints);
|
||||||
|
self.finalsSpan = try gAnimationSys.slots.allocate(@intCast(numJoints));
|
||||||
|
|
||||||
|
if (self.resolverRef) |ref| {
|
||||||
|
if (ref.vtable.onSkeletonSet) |f| {
|
||||||
|
f(ref.ptr, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.jointRemap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSkeleton(self: *@This(), skeleton: []const u8) void {
|
||||||
|
self.setSkeletonByName(core.MakeName(skeleton)) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addResolver(self: *@This(), comptime Resolver: type) !*Resolver {
|
||||||
|
const resolver = try Resolver.create(allocator);
|
||||||
|
try resolver.onSkeletonSet(self);
|
||||||
|
|
||||||
|
self.resolverRef = core.refFromPtr(AnimResolverInterface, resolver);
|
||||||
|
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeResolver(self: *@This()) void {
|
||||||
|
if (self.resolverRef) |ref| {
|
||||||
|
ref.vtable.destroy(ref.ptr);
|
||||||
|
self.resolverRef = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(self: *@This(), dt: f64) void {
|
||||||
|
if (self.skeleton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a resolver is present, use that to update my the finals instead of the default function below
|
||||||
|
if (self.resolverRef) |ref| {
|
||||||
|
ref.vtable.resolve(ref.ptr, dt, self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.defaultSample(dt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.modelToFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn defaultSample(self: *@This(), dt: f64) bool {
|
||||||
|
if (self.track == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const track = self.track.?;
|
||||||
|
if (track.endTime < 0.01)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const skeleton = self.skeleton.?;
|
||||||
|
self.playback += @as(f32, @floatCast(dt)) * self.playbackRate;
|
||||||
|
|
||||||
|
while (self.playback > track.endTime) {
|
||||||
|
self.playback -= track.endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
var samplingJob: ozz.SamplingJob = .{
|
||||||
|
.ratio = self.playback / track.endTime,
|
||||||
|
.animation = track.animation,
|
||||||
|
.context = self.sjc,
|
||||||
|
.output = ozz.makeSpan(self.locals.items),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!samplingJob.run()) {
|
||||||
|
core.engine_errs("sampling job failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ltmJob: ozz.LocalToModelJob = .{
|
||||||
|
.skeleton = skeleton.sk,
|
||||||
|
.input = ozz.makeSpan(self.locals.items),
|
||||||
|
.output = ozz.makeSpan(self.models.items),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ltmJob.run()) {
|
||||||
|
core.engine_errs("local to model job failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commitLocalToModel(self: *@This(), input: []ozz.SoaTransform) void {
|
||||||
|
self.localToModel(input, self.models.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn localToModel(self: *@This(), input: []ozz.SoaTransform, output: []ozz.Float4x4) void {
|
||||||
|
var ltmJob: ozz.LocalToModelJob = .{
|
||||||
|
.skeleton = self.skeleton.?.sk,
|
||||||
|
.input = ozz.makeSpan(input),
|
||||||
|
.output = ozz.makeSpan(output),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ltmJob.run()) {
|
||||||
|
core.engine_errs("local to model job failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sampleAnimation(self: *@This(), time: f32, track: *AnimationTrack, output: []ozz.SoaTransform) void {
|
||||||
|
var samplingJob: ozz.SamplingJob = .{
|
||||||
|
.ratio = time / track.endTime,
|
||||||
|
.animation = track.animation,
|
||||||
|
.context = self.sjc,
|
||||||
|
.output = ozz.makeSpan(output),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!samplingJob.run()) {
|
||||||
|
core.engine_errs("sampling job failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modelToFinal(self: *@This()) void {
|
||||||
|
if (self.jointRemap == null) {
|
||||||
|
if (self.entity.get(graphics.StaticMesh)) |meshComponent| {
|
||||||
|
if (meshComponent.mesh) |mesh| {
|
||||||
|
self.jointRemap = mesh.jointRemap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const skeleton = self.skeleton.?;
|
||||||
|
for (self.models.items, 0..) |model, i| {
|
||||||
|
const transform: core.Mat = @bitCast(model);
|
||||||
|
|
||||||
|
// const p: core.zm.Vec = .{ 0, 0, 0, 1 };
|
||||||
|
// graphics.debugSphere(core.Vectorf.fromZm(core.zm.mul(p, transform)), 0.03, .{
|
||||||
|
// .color = if (i == 15) .{ .x = 1 } else .{ .y = 1 },
|
||||||
|
// });
|
||||||
|
|
||||||
|
const final = core.zm.mul(skeleton.inverseBinds.items[i], transform);
|
||||||
|
// joint remap ozz -> gltf
|
||||||
|
if (self.jointRemap) |jr| {
|
||||||
|
// core.engine_log("{d} xx {d}", .{ i, jr[i] });
|
||||||
|
self.finals.items[@intCast(jr[i])] = final;
|
||||||
|
} else {
|
||||||
|
self.finals.items[i] = final;
|
||||||
|
}
|
||||||
|
// core.engine_log(
|
||||||
|
// "[{d}] {d} {d} {d} {d}, {d} {d} {d} {d}",
|
||||||
|
// .{ i, transform[0][0], transform[0][1], transform[0][2], transform[0][3], transform[1][0], transform[1][1], transform[1][2], transform[1][3] },
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setAnimationByName(self: *@This(), _name: core.Name) !void {
|
||||||
|
var name = _name;
|
||||||
|
self.track = gAnimationSys.animTracks.get(name.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setAnimation(self: *@This(), path: []const u8) void {
|
||||||
|
const name = core.MakeName(path);
|
||||||
|
self.setAnimationByName(name) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.sjc.destroy();
|
||||||
|
self.removeResolver();
|
||||||
|
self.finals.deinit(allocator);
|
||||||
|
self.locals.deinit(allocator);
|
||||||
|
self.models.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub var BaseContainer: *core.SparseMap(@This()) = undefined;
|
||||||
|
pub const ComponentName = "Animator";
|
||||||
|
pub const ScriptExports: []const []const u8 = &.{};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AnimationSystem = struct {
|
||||||
|
backingAllocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
slots: MergedSpans,
|
||||||
|
|
||||||
|
// Only AnimationTrack and Skeletons are made using the ArenaAllocator
|
||||||
|
animTracks: std.AutoHashMapUnmanaged(u32, *AnimationTrack) = .{},
|
||||||
|
skeletons: std.AutoHashMapUnmanaged(u32, *Skeleton) = .{},
|
||||||
|
|
||||||
|
sharedArena: [2]std.heap.ArenaAllocator, // could be a good usecase for a fat bump arena
|
||||||
|
shared: [2]std.ArrayListUnmanaged(MatrixUploads) = .{ .{}, .{} },
|
||||||
|
sharedLocks: [2]std.Thread.Mutex = .{ .{}, .{} }, // could be a good usecase for a fat bump arena
|
||||||
|
|
||||||
|
pub const MatrixUploads = struct {
|
||||||
|
offset: u32,
|
||||||
|
matrices: std.ArrayListUnmanaged(core.Mat) = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
pub const RendererInterfaceVTable = graphics.RendererInterface.from(@This());
|
||||||
|
|
||||||
|
pub fn preTick(self: *@This(), dt: f64) !void {
|
||||||
|
_ = self;
|
||||||
|
var z1 = core.tracy.ZoneN(@src(), "animation system tick");
|
||||||
|
defer z1.End();
|
||||||
|
|
||||||
|
for (Animator.BaseContainer.list.items) |animator| {
|
||||||
|
animator.update(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newAnimTrack(self: *@This(), _name: core.Name, anim: *ozz.Animation) !void {
|
||||||
|
var name = _name;
|
||||||
|
const new = try self.arenaAllocator().create(AnimationTrack);
|
||||||
|
new.* = .{
|
||||||
|
.animation = anim,
|
||||||
|
.endTime = anim.getDuration(),
|
||||||
|
};
|
||||||
|
try self.animTracks.put(self.backingAllocator, name.handle(), new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newSkeleton(self: *@This(), _name: core.Name, sk: *ozz.Skeleton) !void {
|
||||||
|
const new = try self.arenaAllocator().create(Skeleton);
|
||||||
|
new.* = .{
|
||||||
|
.sk = sk,
|
||||||
|
};
|
||||||
|
var name = _name;
|
||||||
|
|
||||||
|
try new.buildJointMap(self.arenaAllocator());
|
||||||
|
|
||||||
|
try new.inverseBinds.resize(self.arenaAllocator(), new.sk.numJoints());
|
||||||
|
|
||||||
|
if (new.inverseBinds.items.len > 256) {
|
||||||
|
@panic("too many bones in skeleton, not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindModels = std.ArrayList(ozz.Float4x4).init(self.backingAllocator);
|
||||||
|
defer bindModels.deinit();
|
||||||
|
|
||||||
|
try bindModels.resize(new.sk.numJoints());
|
||||||
|
|
||||||
|
var ltmJob: ozz.LocalToModelJob = .{
|
||||||
|
.skeleton = new.sk,
|
||||||
|
.input = new.sk.getRestPoseModel(),
|
||||||
|
.output = ozz.makeSpan(bindModels.items),
|
||||||
|
};
|
||||||
|
|
||||||
|
core.engine_log("creating bind pose {d} joints", .{new.inverseBinds.items.len});
|
||||||
|
|
||||||
|
if (!ltmJob.run()) {
|
||||||
|
core.engine_logs("unable to get bind pose");
|
||||||
|
return error.UnableToLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (bindModels.items, 0..) |bind, i| {
|
||||||
|
// const p: core.zm.Vec = .{ 0, 0, 0, 1 };
|
||||||
|
// graphics.debugSphere(core.Vectorf.fromZm(core.zm.mul(p, @as(core.Mat, @bitCast(bind)))), 0.1, .{ .duration = 100 });
|
||||||
|
|
||||||
|
new.inverseBinds.items[i] = core.zm.inverse(@as(core.Mat, @bitCast(bind)));
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.skeletons.put(self.backingAllocator, name.handle(), new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arenaAllocator(self: *@This()) std.mem.Allocator {
|
||||||
|
return self.arena.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getShared(self: @This(), fi: u32) []const MatrixUploads {
|
||||||
|
return self.shared[fi].items;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendShared(self: *@This(), frameIndex: u32) void {
|
||||||
|
const fi: usize = @intCast(frameIndex);
|
||||||
|
|
||||||
|
self.sharedLocks[fi].lock();
|
||||||
|
defer self.sharedLocks[fi].unlock();
|
||||||
|
|
||||||
|
_ = self.sharedArena[fi].reset(.retain_capacity);
|
||||||
|
|
||||||
|
const allocator = self.sharedArena[fi].allocator();
|
||||||
|
const shared = &self.shared[fi];
|
||||||
|
shared.* = .{};
|
||||||
|
|
||||||
|
for (Animator.BaseContainer.list.items) |animator| {
|
||||||
|
var upload: MatrixUploads = .{ .offset = animator.finalsSpan.start };
|
||||||
|
// core.engine_log(
|
||||||
|
// "finalsSpan size offset{d} {d} animator finals {d}\n",
|
||||||
|
// .{
|
||||||
|
// animator.finalsSpan.start,
|
||||||
|
// animator.finalsSpan.size,
|
||||||
|
// animator.finals.items.len
|
||||||
|
// });
|
||||||
|
|
||||||
|
upload.matrices.resize(allocator, animator.finalsSpan.size) catch unreachable;
|
||||||
|
|
||||||
|
for (animator.finals.items, 0..) |final, i| {
|
||||||
|
upload.matrices.items[i] = final;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.append(allocator, upload) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try alloc.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.backingAllocator = alloc,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(alloc),
|
||||||
|
.sharedArena = .{
|
||||||
|
std.heap.ArenaAllocator.init(alloc),
|
||||||
|
std.heap.ArenaAllocator.init(alloc),
|
||||||
|
},
|
||||||
|
.slots = try MergedSpans.init(alloc, vk_constants.MAX_SKIN_SLOTS),
|
||||||
|
};
|
||||||
|
|
||||||
|
gAnimationSys = self;
|
||||||
|
Animator.allocator = alloc;
|
||||||
|
|
||||||
|
try core.defineComponent(Animator, alloc);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
core.engine_logs("deinitializing animation system");
|
||||||
|
{
|
||||||
|
core.engine_log("skeleton count {d}", .{self.skeletons.count()});
|
||||||
|
var iter = self.skeletons.iterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
i.value_ptr.*.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
core.engine_log("animTracks count {d}", .{self.animTracks.count()});
|
||||||
|
var iter = self.animTracks.iterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
i.value_ptr.*.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.sharedArena) |arena| {
|
||||||
|
arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Animator.BaseContainer.list.items) |animator| {
|
||||||
|
animator.deinit();
|
||||||
|
}
|
||||||
|
self.slots.deinit();
|
||||||
|
core.undefineComponent(Animator);
|
||||||
|
self.arena.deinit();
|
||||||
|
self.skeletons.deinit(self.backingAllocator);
|
||||||
|
self.animTracks.deinit(self.backingAllocator);
|
||||||
|
self.backingAllocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var gAnimationSys: *AnimationSystem = undefined;
|
||||||
|
|
||||||
|
pub fn getSkeletonByName(_name: core.Name) ?*Skeleton {
|
||||||
|
var name = _name;
|
||||||
|
return gAnimationSys.skeletons.get(name.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
const graphics = @import("../graphics.zig");
|
||||||
|
const MergedSpans = core.MergedSpans;
|
||||||
|
const vk_constants = @import("../vk_constants.zig");
|
||||||
|
|
||||||
|
const anim_resolver = @import("animResolver.zig");
|
||||||
|
const AnimResolverRef = anim_resolver.AnimResolverRef;
|
||||||
|
const AnimResolverInterface = anim_resolver.AnimResolverInterface;
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
pub const AnimationLoader = struct {
|
||||||
|
pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Animation", @This());
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
sys: *animation_system.AnimationSystem,
|
||||||
|
|
||||||
|
pub fn discardAll(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
|
||||||
|
const animation = ozz.Animation.create();
|
||||||
|
|
||||||
|
const mapping = core.fs().loadFile(propertiesBag.?.path) catch return error.UnableToLoad;
|
||||||
|
defer core.fs().unmap(mapping);
|
||||||
|
animation.loadFromBytes(mapping.bytes);
|
||||||
|
|
||||||
|
self.sys.newAnimTrack(assetRef.name, animation) catch return error.UnableToLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.sys = animation_system.gAnimationSys,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SkeletonLoader = struct {
|
||||||
|
pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Skeleton", @This());
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
sys: *animation_system.AnimationSystem,
|
||||||
|
|
||||||
|
pub fn discardAll(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
|
||||||
|
const sk = ozz.Skeleton.create();
|
||||||
|
|
||||||
|
const mapping = core.fs().loadFile(propertiesBag.?.path) catch return error.UnableToLoad;
|
||||||
|
defer core.fs().unmap(mapping);
|
||||||
|
sk.loadFromBytes(mapping.bytes);
|
||||||
|
|
||||||
|
self.sys.newSkeleton(assetRef.name, sk) catch return error.UnableToLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.sys = animation_system.gAnimationSys,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var gSkeletonLoader: *SkeletonLoader = undefined;
|
||||||
|
pub var gAnimationLoader: *AnimationLoader = undefined;
|
||||||
|
|
||||||
|
pub fn initLoaders() !void {
|
||||||
|
gSkeletonLoader = try core.createObject(SkeletonLoader, .{});
|
||||||
|
gAnimationLoader = try core.createObject(AnimationLoader, .{});
|
||||||
|
|
||||||
|
try assets.gAssetSys.registerLoader(gSkeletonLoader);
|
||||||
|
try assets.gAssetSys.registerLoader(gAnimationLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animation_system = @import("animationSystem.zig");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const core = @import("core");
|
||||||
|
const std = @import("std");
|
||||||
|
const ozz = @import("ozz");
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
const MeshConfig = struct {
|
||||||
|
info: CookInfo = .{ .assetType = "Mesh" }, // there must always be a CookInfo field
|
||||||
|
sourceType: []const u8 = "obj",
|
||||||
|
animated: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const extList = [_][]const u8{ "gltf", "obj", "glb" };
|
||||||
|
pub fn generateFunction(allocator: std.mem.Allocator, path: []const u8, out: *std.ArrayList(u8)) GenerateError!void {
|
||||||
|
_ = allocator;
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
const ext = core.getFileExtension(path)[1..];
|
||||||
|
|
||||||
|
var config: MeshConfig = .{};
|
||||||
|
|
||||||
|
for (extList) |e| {
|
||||||
|
if (std.mem.eql(u8, e, ext)) {
|
||||||
|
config.sourceType = e;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, e, "glb")) {
|
||||||
|
config.sourceType = "gltf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.json.stringify(
|
||||||
|
config,
|
||||||
|
.{ .whitespace = .indent_4 },
|
||||||
|
out.writer(),
|
||||||
|
) catch return GenerateError.UnableToGenerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cookObj(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) cook.CookResult {
|
||||||
|
const rawFileBytes = cook.loadFileAlloc(allocator, dir, path) catch unreachable;
|
||||||
|
defer allocator.free(rawFileBytes);
|
||||||
|
|
||||||
|
var out = std.ArrayList(u8).init(allocator);
|
||||||
|
|
||||||
|
var vertices = std.ArrayList(Vertex).init(allocator);
|
||||||
|
defer vertices.deinit();
|
||||||
|
|
||||||
|
var objs = obj.loadObjBytes(rawFileBytes, allocator) catch unreachable;
|
||||||
|
defer objs.deinit();
|
||||||
|
|
||||||
|
if (objs.meshes.items.len > 0) {
|
||||||
|
mesh.loadObjMeshVertices(&vertices, objs.meshes.items[0]) catch unreachable;
|
||||||
|
for (vertices.items) |vert| {
|
||||||
|
out.appendSlice(&@as([@sizeOf(Vertex)]u8, @bitCast(vert))) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.bytes = out,
|
||||||
|
.result = .Success,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return .{
|
||||||
|
.bytes = out,
|
||||||
|
.result = .Failure,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensureGltf2ozz(allocator: std.mem.Allocator) !void {
|
||||||
|
const suffix = if (builtin.os.tag == .windows) ".exe" else "";
|
||||||
|
std.fs.cwd().access("zig-out/tools/gltf2ozz" ++ suffix, .{}) catch {
|
||||||
|
const argv: []const []const u8 = &.{ "zig", "build", "tools" };
|
||||||
|
|
||||||
|
core.engine_log("gltf2ozz missing, building it...", .{});
|
||||||
|
|
||||||
|
var child = std.process.Child.init(argv, allocator);
|
||||||
|
child.stdin_behavior = .Ignore;
|
||||||
|
child.stdout_behavior = .Pipe;
|
||||||
|
child.stderr_behavior = .Pipe;
|
||||||
|
child.cwd = ".";
|
||||||
|
|
||||||
|
switch (try child.spawnAndWait()) {
|
||||||
|
.Exited => |value| {
|
||||||
|
if (value == 0) {
|
||||||
|
core.engine_log("gltf2ozz built", .{});
|
||||||
|
} else {
|
||||||
|
core.engine_logs("unable to build gltf2ozz");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Signal => {
|
||||||
|
core.engine_logs("unable to build gltf2ozz");
|
||||||
|
},
|
||||||
|
.Stopped => {},
|
||||||
|
.Unknown => {
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
core.engine_log("gltf2ozz found", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cookAnimations(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, config: MeshConfig) !void {
|
||||||
|
_ = config;
|
||||||
|
|
||||||
|
// 1. check if it has an associated .ozzconfig file.
|
||||||
|
|
||||||
|
const gltf2OzzAbs = try std.fs.cwd().realpathAlloc(allocator, "zig-out/tools/gltf2ozz.exe");
|
||||||
|
defer allocator.free(gltf2OzzAbs);
|
||||||
|
|
||||||
|
const ozzconfig = try std.fmt.allocPrint(allocator, "{s}.ozzconfig", .{path});
|
||||||
|
defer allocator.free(ozzconfig);
|
||||||
|
|
||||||
|
const fileArg = try std.fmt.allocPrint(allocator, "--file={s}", .{core.getBasePath(path)});
|
||||||
|
defer allocator.free(fileArg);
|
||||||
|
|
||||||
|
const configArg = try std.fmt.allocPrint(allocator, "--config_file={s}", .{core.getBasePath(ozzconfig)});
|
||||||
|
defer allocator.free(configArg);
|
||||||
|
|
||||||
|
// todo, fix this later, idrc right now.
|
||||||
|
const newConfigArg = try std.fmt.allocPrint(allocator, "--config_dump_reference={s}", .{core.getBasePath(ozzconfig)});
|
||||||
|
defer allocator.free(newConfigArg);
|
||||||
|
|
||||||
|
const absFile = try dir.realpathAlloc(allocator, path);
|
||||||
|
defer allocator.free(absFile);
|
||||||
|
|
||||||
|
var argv: []const []const u8 = &.{
|
||||||
|
gltf2OzzAbs,
|
||||||
|
fileArg,
|
||||||
|
configArg,
|
||||||
|
};
|
||||||
|
|
||||||
|
dir.access(ozzconfig, .{}) catch {
|
||||||
|
argv = &.{
|
||||||
|
gltf2OzzAbs,
|
||||||
|
fileArg,
|
||||||
|
newConfigArg,
|
||||||
|
};
|
||||||
|
|
||||||
|
core.engine_log("creating ozz config for file, marked as animated but no animation data", .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
// std.debug.print("{s} {s} {s} cwd = {s}\n", .{ argv[0], argv[1], argv[2], core.getFolder(absFile) });
|
||||||
|
|
||||||
|
const result = try std.process.Child.run(.{
|
||||||
|
.argv = argv,
|
||||||
|
.allocator = allocator,
|
||||||
|
.cwd = core.getFolder(absFile),
|
||||||
|
.max_output_bytes = 150 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
defer allocator.free(result.stdout);
|
||||||
|
defer allocator.free(result.stderr);
|
||||||
|
|
||||||
|
var success: bool = true;
|
||||||
|
switch (result.term) {
|
||||||
|
.Exited => |value| {
|
||||||
|
if (value != 0) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Signal => {
|
||||||
|
success = false;
|
||||||
|
},
|
||||||
|
.Stopped => {
|
||||||
|
success = false;
|
||||||
|
// no-op should be ok?
|
||||||
|
},
|
||||||
|
.Unknown => {
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
core.engine_log("generated animations for {s}", .{path});
|
||||||
|
} else {
|
||||||
|
core.engine_log("error generating animations {s} stdout:\n{s}\n stderr:{s}\n", .{ path, result.stdout, result.stderr });
|
||||||
|
}
|
||||||
|
// 2. if so, run it through gltf2ozz with that file.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cookGltf(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, config: MeshConfig) cook.CookResult {
|
||||||
|
core.engine_logs("gltf cooking not implemeted");
|
||||||
|
const out = std.ArrayList(u8).init(allocator);
|
||||||
|
|
||||||
|
// check if it's animated. if it's animated, then invoke gltf2ozz and create a .ozzconfig file and
|
||||||
|
// make a subfolder called
|
||||||
|
|
||||||
|
if (config.animated) {
|
||||||
|
// if gltf2ozz isn't there then we have to call zig build tools
|
||||||
|
ensureGltf2ozz(allocator) catch unreachable;
|
||||||
|
|
||||||
|
cookAnimations(allocator, dir, path, config) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .bytes = out, .result = .Failure };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookFunction(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
dir: std.fs.Dir,
|
||||||
|
path: []const u8,
|
||||||
|
params: cook.CookParams,
|
||||||
|
) cook.CookResult {
|
||||||
|
core.engine_log("{s}", .{params.cookFileName});
|
||||||
|
const fc = cook.loadFileAlloc(allocator, dir, params.cookFileName) catch unreachable;
|
||||||
|
defer allocator.free(fc);
|
||||||
|
|
||||||
|
const config = std.json.parseFromSlice(MeshConfig, allocator, fc[0 .. fc.len - 1], .{}) catch unreachable;
|
||||||
|
defer config.deinit();
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, config.value.sourceType, "obj")) {
|
||||||
|
return cookObj(allocator, dir, path);
|
||||||
|
} else {
|
||||||
|
return cookGltf(allocator, dir, path, config.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initCooker(allocator: std.mem.Allocator) !void {
|
||||||
|
_ = allocator;
|
||||||
|
const registry = assets.cook.getRegistry();
|
||||||
|
|
||||||
|
try registry.install("Mesh", generateFunction, cookFunction, &.{
|
||||||
|
".obj",
|
||||||
|
".gltf",
|
||||||
|
".glb",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinitCooker() void {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const cook = assets.cook;
|
||||||
|
const CookInfo = assets.cook.CookInfo;
|
||||||
|
const GenerateError = assets.cook.GenerateError;
|
||||||
|
const core = @import("core");
|
||||||
|
const obj = @import("objLoader");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const mesh = @import("../mesh.zig");
|
||||||
|
const Mesh = mesh.Mesh;
|
||||||
|
const Vertex = mesh.MeshVertex;
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
const TextureConfig = struct {
|
||||||
|
info: CookInfo = .{ .assetType = "Texture" }, // there must always be a CookInfo field
|
||||||
|
sourceType: []const u8 = "png",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generateFunction(allocator: std.mem.Allocator, path: []const u8, out: *std.ArrayList(u8)) GenerateError!void {
|
||||||
|
_ = allocator;
|
||||||
|
_ = path;
|
||||||
|
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
std.json.stringify(
|
||||||
|
TextureConfig{},
|
||||||
|
.{ .whitespace = .indent_4 },
|
||||||
|
out.writer(),
|
||||||
|
) catch return GenerateError.UnableToGenerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookFunction(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
dir: std.fs.Dir,
|
||||||
|
path: []const u8,
|
||||||
|
params: cook.CookParams,
|
||||||
|
) cook.CookResult {
|
||||||
|
_ = params;
|
||||||
|
|
||||||
|
const rawFileBytes = cook.loadFileAlloc(allocator, dir, path) catch unreachable;
|
||||||
|
defer allocator.free(rawFileBytes);
|
||||||
|
// 1. load the file, and create a bytes buffer
|
||||||
|
var contents = png.PngContents.initFromBytes(allocator, path, rawFileBytes) catch unreachable;
|
||||||
|
defer contents.deinit();
|
||||||
|
|
||||||
|
// png.PngContents.initFromBytes(allocator: std.mem.Allocator, pathName: []const u8, pngFileContents: []const u8)
|
||||||
|
// 2. use the PngContents function to cook it.
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.bytes = contents.toBuffer() catch unreachable,
|
||||||
|
.result = .Success,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initCooker(allocator: std.mem.Allocator) !void {
|
||||||
|
_ = allocator;
|
||||||
|
const registry = assets.cook.getRegistry();
|
||||||
|
|
||||||
|
try registry.install("Texture", generateFunction, cookFunction, &.{
|
||||||
|
".png",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinitCooker() void {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const cook = assets.cook;
|
||||||
|
const CookInfo = assets.cook.CookInfo;
|
||||||
|
const GenerateError = assets.cook.GenerateError;
|
||||||
|
const core = @import("core");
|
||||||
|
const png = core.png;
|
||||||
|
|
@ -0,0 +1,421 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const vkd_utils = @import("vk_renderer/vkd_utils.zig");
|
||||||
|
const core = @import("core");
|
||||||
|
const graphics = @import("graphics.zig");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const debug_vert = @import("debug_vert");
|
||||||
|
const debug_frag = @import("debug_frag");
|
||||||
|
const tracy = core.tracy;
|
||||||
|
const gpd = graphics.gpu_pipe_data;
|
||||||
|
|
||||||
|
pub const DebugLine = struct {
|
||||||
|
start: core.Vectorf,
|
||||||
|
end: core.Vectorf,
|
||||||
|
|
||||||
|
pub fn resolve(self: @This(), _: anytype) core.Transform {
|
||||||
|
var delta = self.start.sub(self.end);
|
||||||
|
const d = delta.normalize();
|
||||||
|
const axz = std.math.atan2(-d.z, d.x) + core.radians(180.0);
|
||||||
|
const ay = -std.math.asin(d.y);
|
||||||
|
const mat1 = core.zm.matFromRollPitchYaw(0, 0, ay);
|
||||||
|
const mat2 = core.zm.rotationY(axz);
|
||||||
|
const len = delta.length();
|
||||||
|
return core.zm.mul(core.zm.mul(
|
||||||
|
core.zm.mul(mat1, mat2),
|
||||||
|
core.zm.scaling(len, len, len),
|
||||||
|
), core.zm.translationV(self.start.toZm()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DebugSphere = struct {
|
||||||
|
position: core.Vectorf,
|
||||||
|
radius: f32,
|
||||||
|
rotation: core.Quat,
|
||||||
|
|
||||||
|
pub fn resolve(self: @This(), _: anytype) core.Transform {
|
||||||
|
return core.zm.mul(core.zm.mul(
|
||||||
|
core.zm.matFromQuat(self.rotation),
|
||||||
|
core.zm.scaling(self.radius, self.radius, self.radius),
|
||||||
|
), core.zm.translationV(self.position.toZm()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DebugBox = struct {
|
||||||
|
position: core.Vectorf,
|
||||||
|
extents: core.Vectorf,
|
||||||
|
rotation: core.Quat,
|
||||||
|
|
||||||
|
pub fn resolve(self: @This(), _: anytype) core.Transform {
|
||||||
|
return core.zm.mul(core.zm.mul(
|
||||||
|
core.zm.matFromQuat(self.rotation),
|
||||||
|
core.zm.scalingV(self.extents.toZm()),
|
||||||
|
), core.zm.translationV(self.position.toZm()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebugPrimitiveType = enum(u8) {
|
||||||
|
line = 0,
|
||||||
|
sphere = 1,
|
||||||
|
box = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DebugPrimitive = struct {
|
||||||
|
primitive: union(DebugPrimitiveType) {
|
||||||
|
line: DebugLine,
|
||||||
|
sphere: DebugSphere,
|
||||||
|
box: DebugBox,
|
||||||
|
},
|
||||||
|
color: core.Vectorf = .{ .x = 0.0, .y = 1.0, .z = 0.0 },
|
||||||
|
duration: f32 = 0.0,
|
||||||
|
|
||||||
|
pub fn resolve(self: @This()) core.Transform {
|
||||||
|
// comptime core.asserts(@sizeOf(DebugPrimitiveGpu) == DebugPrimitiveGpu.TargetSize, "");
|
||||||
|
|
||||||
|
switch (self.primitive) {
|
||||||
|
.line => |inner| {
|
||||||
|
return inner.resolve(.{});
|
||||||
|
},
|
||||||
|
.sphere => |inner| {
|
||||||
|
return inner.resolve(.{});
|
||||||
|
},
|
||||||
|
.box => |inner| {
|
||||||
|
return inner.resolve(.{});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
|
||||||
|
// return core.implement_func_for_tagged_union_nonull(self.primitive, "resolve", core.Transform, .{});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebugPrimitiveGpu = struct {
|
||||||
|
const UnpaddedSize = @sizeOf(core.Transform) + @sizeOf(core.Vectorf);
|
||||||
|
const TargetSize = 80;
|
||||||
|
|
||||||
|
model: core.Transform,
|
||||||
|
color: core.Vectorf,
|
||||||
|
|
||||||
|
pad: [TargetSize - UnpaddedSize]u8 = std.mem.zeroes([TargetSize - UnpaddedSize]u8),
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebugDrawSharedInstance = struct {};
|
||||||
|
|
||||||
|
const DebugSharedData = struct {
|
||||||
|
drawsThisFrame: std.ArrayListUnmanaged(DebugPrimitive) = .{},
|
||||||
|
lock: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.drawsThisFrame.deinit(allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const objectCount = 2048;
|
||||||
|
|
||||||
|
// Debug draw system also an example of how to do plugins in this engine
|
||||||
|
pub const DebugDrawSubsystem = struct {
|
||||||
|
|
||||||
|
// Interfaces and tables
|
||||||
|
pub const RendererInterfaceVTable = graphics.RendererInterface.from(@This());
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
// Member functions
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
debugDraws: core.RingQueueU(DebugPrimitive),
|
||||||
|
meshes: [@as(usize, @intCast(@intFromEnum(DebugPrimitiveType.box) + 1))]core.Name = .{ undefined, undefined, undefined },
|
||||||
|
gc: *graphics.NeonVkContext = undefined,
|
||||||
|
pipeData: gpd.GpuPipeData = undefined,
|
||||||
|
mappedBuffers: []gpd.GpuMappingData(DebugPrimitiveGpu) = undefined,
|
||||||
|
material: *graphics.Material = undefined,
|
||||||
|
materialName: core.Name = core.Name.MakeComptime("mat_debugsys"),
|
||||||
|
|
||||||
|
deltaTime: f64 = 0,
|
||||||
|
|
||||||
|
sharedData: [graphics.NumFrames]DebugSharedData = .{ .{}, .{} },
|
||||||
|
|
||||||
|
indirectStaging: graphics.NeonVkBuffer = undefined,
|
||||||
|
indirectGpu: graphics.NeonVkBuffer = undefined,
|
||||||
|
|
||||||
|
const Primitives = [_]assets.AssetImportReference{
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_sphere", "meshes/primitive_sphere.obj"),
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_box", "meshes/primitive_box.obj"),
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_line", "meshes/primitive_line.obj"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn prepareSubsystem(self: *@This(), gc: *graphics.NeonVkContext) !void {
|
||||||
|
self.gc = gc;
|
||||||
|
|
||||||
|
// assign debug meshes
|
||||||
|
self.meshes[@as(usize, @intCast(@intFromEnum(DebugPrimitiveType.sphere)))] = core.MakeName("m_primitive_sphere");
|
||||||
|
self.meshes[@as(usize, @intCast(@intFromEnum(DebugPrimitiveType.box)))] = core.MakeName("m_primitive_box");
|
||||||
|
self.meshes[@as(usize, @intCast(@intFromEnum(DebugPrimitiveType.line)))] = core.MakeName("m_primitive_line");
|
||||||
|
try self.createPipeData();
|
||||||
|
try self.createMaterial();
|
||||||
|
|
||||||
|
// create indirect command buffers
|
||||||
|
self.indirectStaging = try gc.vkAllocator.createStagingBuffer(4096 * @sizeOf(vk.DrawIndexedIndirectCommand), "debug draw indirect staging buffer");
|
||||||
|
self.indirectGpu = try gc.vkAllocator.createIndirectCommandBuffer(4096 * @sizeOf(vk.DrawIndexedIndirectCommand), "debug draw indirect command buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uploadIndirectCommands(self: *@This(), cmd: vk.CommandBuffer, count: u32) void {
|
||||||
|
vkd_utils.copyStagingSlice(vk.DrawIndexedIndirectCommand, cmd, .{
|
||||||
|
.src = &self.indirectStaging,
|
||||||
|
.dst = &self.indirectGpu,
|
||||||
|
.size = count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createPipeData(self: *@This()) !void {
|
||||||
|
var dataBuilder = gpd.GpuPipeDataBuilder.init(self.allocator, self.gc);
|
||||||
|
defer dataBuilder.deinit();
|
||||||
|
dataBuilder.setObjectCount(objectCount);
|
||||||
|
try dataBuilder.addBufferBinding(DebugPrimitiveGpu, .storage_buffer, .{ .vertex_bit = true }, .storageBuffer);
|
||||||
|
self.pipeData = try dataBuilder.build("debug draws");
|
||||||
|
self.mappedBuffers = try self.pipeData.mapBuffers(self.gc, DebugPrimitiveGpu, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createMaterial(self: *@This()) !void {
|
||||||
|
var gc: *graphics.NeonVkContext = self.gc;
|
||||||
|
|
||||||
|
const vert_spv = debug_vert.spv();
|
||||||
|
const frag_spv = debug_frag.spv();
|
||||||
|
|
||||||
|
var pipelineBuilder = try graphics.NeonVkPipelineBuilder.init(
|
||||||
|
gc.dev,
|
||||||
|
gc.vkd,
|
||||||
|
gc.allocator,
|
||||||
|
gc.vkAllocator,
|
||||||
|
vert_spv,
|
||||||
|
frag_spv,
|
||||||
|
);
|
||||||
|
defer pipelineBuilder.deinit();
|
||||||
|
|
||||||
|
try pipelineBuilder.add_mesh_description();
|
||||||
|
try pipelineBuilder.add_layout(self.gc.globalDescriptorLayout);
|
||||||
|
try pipelineBuilder.add_layout(self.pipeData.descriptorSetLayout);
|
||||||
|
try pipelineBuilder.add_depth_stencil();
|
||||||
|
pipelineBuilder.set_polygon_mode(.line);
|
||||||
|
pipelineBuilder.set_topology(.triangle_list);
|
||||||
|
try pipelineBuilder.init_triangle_pipeline(gc.actual_extent);
|
||||||
|
|
||||||
|
const materialName = self.materialName;
|
||||||
|
const material = try gc.allocator.create(graphics.Material);
|
||||||
|
material.* = graphics.Material{
|
||||||
|
.materialName = materialName,
|
||||||
|
.pipeline = (try pipelineBuilder.build(gc.renderPass)).?,
|
||||||
|
.layout = pipelineBuilder.pipelineLayout,
|
||||||
|
};
|
||||||
|
|
||||||
|
try gc.add_material(material);
|
||||||
|
|
||||||
|
self.material = material;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer Ineterface Implementation
|
||||||
|
// pub fn preDraw(self: *@This(), frameId: usize) void {
|
||||||
|
// var zone = tracy.ZoneN(@src(), "Debug draw renderer");
|
||||||
|
// defer zone.End();
|
||||||
|
|
||||||
|
// const count: usize = self.debugDraws.count();
|
||||||
|
// var offset: usize = 0;
|
||||||
|
// while (offset < count) : (offset += 1) {
|
||||||
|
// const primitive = self.debugDraws.at(offset).?;
|
||||||
|
// const transform = primitive.resolve();
|
||||||
|
// const color = primitive.color;
|
||||||
|
|
||||||
|
// const object = &self.mappedBuffers[frameId].objects[offset];
|
||||||
|
// object.*.color = color;
|
||||||
|
// object.*.model = transform;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn tick(self: *@This(), dt: f64) void {
|
||||||
|
self.deltaTime = dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendShared(self: *@This(), frameIndex: u32) void {
|
||||||
|
var zone = tracy.ZoneN(@src(), "debug draw- uploading shared");
|
||||||
|
defer zone.End();
|
||||||
|
self.sharedData[frameIndex].lock.lock();
|
||||||
|
defer self.sharedData[frameIndex].lock.unlock();
|
||||||
|
|
||||||
|
var offset: usize = 0;
|
||||||
|
const count = self.debugDraws.count();
|
||||||
|
|
||||||
|
self.sharedData[frameIndex].drawsThisFrame.clearRetainingCapacity();
|
||||||
|
|
||||||
|
while (offset < count) {
|
||||||
|
var primitive: DebugPrimitive = self.debugDraws.pop().?;
|
||||||
|
self.sharedData[frameIndex].drawsThisFrame.append(self.allocator, primitive) catch unreachable;
|
||||||
|
|
||||||
|
primitive.duration -= @as(f32, @floatCast(self.deltaTime));
|
||||||
|
offset += 1;
|
||||||
|
if (primitive.duration >= 0) {
|
||||||
|
// push this primitive so that it goes to the next frame
|
||||||
|
self.debugDraws.push(primitive) catch continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rtPreDraw(self: *@This(), rt: *graphics.RenderThread, cmd: vk.CommandBuffer, frameIndex: u32) void {
|
||||||
|
_ = rt;
|
||||||
|
const shared: *DebugSharedData = &self.sharedData[frameIndex];
|
||||||
|
var offset: usize = 0;
|
||||||
|
shared.lock.lock();
|
||||||
|
defer shared.lock.unlock();
|
||||||
|
const count: usize = shared.drawsThisFrame.items.len;
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
const mapped = self.gc.vkAllocator.mapBuffer(vk.DrawIndexedIndirectCommand, self.indirectStaging) catch unreachable;
|
||||||
|
defer self.gc.vkAllocator.unmapMemory(self.indirectStaging);
|
||||||
|
|
||||||
|
while (offset < count) : (offset += 1) {
|
||||||
|
const primitive: DebugPrimitive = shared.drawsThisFrame.items[offset];
|
||||||
|
|
||||||
|
var mesh: core.Name = undefined;
|
||||||
|
switch (primitive.primitive) {
|
||||||
|
.box => {
|
||||||
|
mesh = self.meshes[2];
|
||||||
|
},
|
||||||
|
.sphere => {
|
||||||
|
mesh = self.meshes[1];
|
||||||
|
},
|
||||||
|
.line => {
|
||||||
|
mesh = self.meshes[0];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexedMesh = graphics.getIndexedMeshByName(mesh).?;
|
||||||
|
mapped[offset] = .{
|
||||||
|
.index_count = indexedMesh.index.size,
|
||||||
|
.instance_count = 1,
|
||||||
|
.first_index = indexedMesh.index.start,
|
||||||
|
.vertex_offset = 0,
|
||||||
|
.first_instance = @intCast(offset),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.uploadIndirectCommands(cmd, @intCast(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rtPostDraw(self: *@This(), rt: *graphics.RenderThread, cmd: vk.CommandBuffer, frameIndex: u32) void {
|
||||||
|
_ = rt;
|
||||||
|
var zone = tracy.ZoneN(@src(), "Debug draw renderer");
|
||||||
|
defer zone.End();
|
||||||
|
const shared: *DebugSharedData = &self.sharedData[frameIndex];
|
||||||
|
|
||||||
|
shared.lock.lock();
|
||||||
|
defer shared.lock.unlock();
|
||||||
|
|
||||||
|
var z1 = tracy.ZoneN(@src(), "Debug draw - ssbo upload");
|
||||||
|
// core.engine_log("count: {d}", .{shared.drawsThisFrame.items.len});
|
||||||
|
for (shared.drawsThisFrame.items, 0..) |primitive, i| {
|
||||||
|
const object = &self.mappedBuffers[frameIndex].objects[i];
|
||||||
|
object.*.color = primitive.color;
|
||||||
|
object.*.model = primitive.resolve();
|
||||||
|
}
|
||||||
|
z1.End();
|
||||||
|
|
||||||
|
var vkd = self.gc.vkd;
|
||||||
|
|
||||||
|
var z2 = tracy.ZoneN(@src(), "Debug draw - pipeline bind");
|
||||||
|
vkd.cmdBindPipeline(cmd, .graphics, self.material.pipeline);
|
||||||
|
var bindOffset: usize = 0;
|
||||||
|
|
||||||
|
const count: usize = shared.drawsThisFrame.items.len;
|
||||||
|
|
||||||
|
const paddedSceneSize = @as(u32, @intCast(self.gc.pad_uniform_buffer_size(@sizeOf(graphics.NeonVkSceneDataGpu))));
|
||||||
|
var startOffset: u32 = paddedSceneSize * @as(u32, @intCast(frameIndex));
|
||||||
|
|
||||||
|
vkd.cmdBindDescriptorSets(cmd, .graphics, self.material.layout, 0, 1, @ptrCast(&self.gc.frameData[frameIndex].globalDescriptorSet), 1, @ptrCast(&startOffset));
|
||||||
|
|
||||||
|
z2.End();
|
||||||
|
|
||||||
|
var z3 = tracy.ZoneN(@src(), "Debug draw - render");
|
||||||
|
var buffers = graphics.getMeshPoolBuffers();
|
||||||
|
vkd.cmdBindVertexBuffers(cmd, 0, 1, @ptrCast(&buffers.vertex.buffer), @ptrCast(&bindOffset));
|
||||||
|
vkd.cmdBindIndexBuffer(cmd, buffers.index.buffer, 0, .uint32);
|
||||||
|
vkd.cmdBindDescriptorSets(cmd, .graphics, self.material.layout, 1, 1, self.pipeData.getDescriptorSet(frameIndex), 0, undefined);
|
||||||
|
vkd.cmdDrawIndexedIndirect(cmd, self.indirectGpu.buffer, 0, @intCast(count), @sizeOf(vk.DrawIndexedIndirectCommand));
|
||||||
|
|
||||||
|
z3.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeonObject Interface Implementation
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.debugDraws = core.RingQueueU(DebugPrimitive).init(allocator, objectCount) catch unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(self: *@This()) void {
|
||||||
|
for (self.mappedBuffers) |*mapped| {
|
||||||
|
mapped.unmap(self.gc);
|
||||||
|
}
|
||||||
|
self.gc.allocator.free(self.mappedBuffers);
|
||||||
|
self.gc.vkAllocator.destroyBuffer(&self.indirectGpu);
|
||||||
|
self.gc.vkAllocator.destroyBuffer(&self.indirectStaging);
|
||||||
|
self.pipeData.deinit(self.allocator, self.gc);
|
||||||
|
self.debugDraws.deinit(self.allocator);
|
||||||
|
for (&self.sharedData) |*shared| {
|
||||||
|
shared.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
core.graphics_logs("shutting down debug draw system");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.shutdown();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var gDebugDrawSys: *DebugDrawSubsystem = undefined;
|
||||||
|
|
||||||
|
pub fn init_debug_draw_subsystem() !void {
|
||||||
|
gDebugDrawSys = try core.gEngine.createObject(DebugDrawSubsystem, .{ .can_tick = true });
|
||||||
|
try gDebugDrawSys.prepareSubsystem(graphics.getContext());
|
||||||
|
try graphics.registerRendererPlugin(gDebugDrawSys);
|
||||||
|
|
||||||
|
try core.installDebugDrawInterface(gDebugDrawSys.allocator, .{
|
||||||
|
.debugSphereFn = debugSphere,
|
||||||
|
.debugBoxFn = debugBox,
|
||||||
|
.debugLineFn = debugLine,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown() void {}
|
||||||
|
|
||||||
|
const DebugDrawParams = core.DebugDrawParams;
|
||||||
|
|
||||||
|
pub fn debugSphere(position: core.Vectorf, radius: f32, params: DebugDrawParams) void {
|
||||||
|
gDebugDrawSys.debugDraws.push(.{
|
||||||
|
.primitive = .{ .sphere = .{ .position = position, .radius = radius, .rotation = params.rotation } },
|
||||||
|
.color = params.color,
|
||||||
|
.duration = params.duration,
|
||||||
|
}) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugLine(start: core.Vectorf, end: core.Vectorf, params: DebugDrawParams) void {
|
||||||
|
gDebugDrawSys.debugDraws.push(.{
|
||||||
|
.primitive = .{ .line = .{
|
||||||
|
.start = start,
|
||||||
|
.end = end,
|
||||||
|
} },
|
||||||
|
.color = params.color,
|
||||||
|
.duration = params.duration,
|
||||||
|
}) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugBox(position: core.Vectorf, extents: core.Vectorf, params: DebugDrawParams) void {
|
||||||
|
gDebugDrawSys.debugDraws.push(.{
|
||||||
|
.primitive = .{
|
||||||
|
.box = .{ .position = position, .extents = extents, .rotation = params.rotation },
|
||||||
|
},
|
||||||
|
.color = params.color,
|
||||||
|
.duration = params.duration,
|
||||||
|
}) catch return;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
// this folder contains
|
||||||
|
const std = @import("std");
|
||||||
|
const root = @import("root");
|
||||||
|
const bl = root.backlog;
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const core = @import("core");
|
||||||
|
const graphics = @import("graphics.zig");
|
||||||
|
const vkinit = graphics.vkinit;
|
||||||
|
const vma = @import("vma");
|
||||||
|
|
||||||
|
const NeonVkContext = graphics.NeonVkContext;
|
||||||
|
const NeonVkBuffer = graphics.NeonVkBuffer;
|
||||||
|
|
||||||
|
const NeonVkAllocator = graphics.NeonVkAllocator;
|
||||||
|
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
||||||
|
|
||||||
|
// so.. given a single descriptor set:
|
||||||
|
// 1. create builder
|
||||||
|
// 2. add buffers for data templates
|
||||||
|
// 3. finalize and build.
|
||||||
|
|
||||||
|
// maybe a better way of doing this:
|
||||||
|
|
||||||
|
// NeonGpuObjectBuilder and NeonGpuObject are an abstraction + automation of
|
||||||
|
|
||||||
|
// vk.DescriptorSet + vk.Buffer and a way to map them.
|
||||||
|
|
||||||
|
// var builder = graphics.NeonGpuObjectBuilder.init(allocator);
|
||||||
|
// builder.addBuffer(SpriteDataGpu, .objectStorageBuffer);
|
||||||
|
// builder.addBuffer(CameraDataGpu, .uniform);
|
||||||
|
// var gpuObject: NeonGpuObject = builder.build();
|
||||||
|
|
||||||
|
// TODO: add a way to unmap multiple buffers.. it has been months now. I have no idea what i meant by this.
|
||||||
|
|
||||||
|
// ---- Proposed API for implemeting extensions into the game ---
|
||||||
|
|
||||||
|
// a GpuPipeData is an API that exists as an API that abstracts both
|
||||||
|
// vulkan buffer allocation and mapping
|
||||||
|
|
||||||
|
pub fn GpuMappingData(comptime ObjectType: type) type {
|
||||||
|
return struct {
|
||||||
|
raw: GpuMappingRaw,
|
||||||
|
objects: []ObjectType, //WARNING! theres a bug do not use this with square operator unless it's a type that's a power of 2
|
||||||
|
trueObjectSize: usize,
|
||||||
|
|
||||||
|
pub fn unmap(self: *@This(), gc: *NeonVkContext) void {
|
||||||
|
self.raw.unmap(gc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const GpuMappingRaw = struct {
|
||||||
|
data: []u8,
|
||||||
|
allocation: vma.Allocation,
|
||||||
|
|
||||||
|
pub fn unmap(self: *@This(), gc: *NeonVkContext) void {
|
||||||
|
gc.vkAllocator.vmaAllocator.unmapMemory(self.allocation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GpuPipeDataBinding = struct {
|
||||||
|
// one slot per frame
|
||||||
|
buffers: []NeonVkBuffer,
|
||||||
|
objectCount: usize,
|
||||||
|
objectSize: usize,
|
||||||
|
frameCount: usize,
|
||||||
|
isFrameBuffer: bool = true,
|
||||||
|
|
||||||
|
pub fn mapBuffers(self: *@This(), gc: *NeonVkContext, comptime MappingType: type) ![]GpuMappingData(MappingType) {
|
||||||
|
var frameIndex: usize = 0;
|
||||||
|
if (self.isFrameBuffer) {
|
||||||
|
try core.assertf(self.frameCount == self.buffers.len, "mismatched frameBuffer {d} != {d}", .{ self.frameCount, self.buffers.len });
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps buffers for these bindings, one for each frame
|
||||||
|
var rv = try gc.allocator.alloc(GpuMappingData(MappingType), self.buffers.len);
|
||||||
|
while (frameIndex < self.buffers.len) : (frameIndex += 1) {
|
||||||
|
const data = try gc.vkAllocator.vmaAllocator.mapMemory(self.buffers[frameIndex].allocation, MappingType);
|
||||||
|
var mapping: []MappingType = undefined;
|
||||||
|
mapping.ptr = @as([*]MappingType, @ptrCast(data));
|
||||||
|
mapping.len = self.objectCount;
|
||||||
|
|
||||||
|
var dataMapping: []u8 = undefined;
|
||||||
|
dataMapping.ptr = @as([*]u8, @ptrCast(data));
|
||||||
|
dataMapping.len = self.objectCount;
|
||||||
|
const gpuMappingData: GpuMappingData(MappingType) = .{
|
||||||
|
.objects = mapping,
|
||||||
|
.trueObjectSize = self.objectSize,
|
||||||
|
.raw = .{ .data = dataMapping, .allocation = self.buffers[frameIndex].allocation },
|
||||||
|
};
|
||||||
|
|
||||||
|
rv[frameIndex] = gpuMappingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), vkAllocator: *NeonVkAllocator) void {
|
||||||
|
for (self.buffers) |*buffers| {
|
||||||
|
buffers.deinit(vkAllocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// High level pipe controls for a gpu data pipe
|
||||||
|
pub const GpuPipeData = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
descriptorSetLayout: vk.DescriptorSetLayout,
|
||||||
|
bindings: []GpuPipeDataBinding,
|
||||||
|
descriptorSets: []vk.DescriptorSet, // one per frame
|
||||||
|
descriptorSetLayoutIsAllocated: bool = false,
|
||||||
|
|
||||||
|
pub fn getDescriptorSet(self: @This(), frameIndex: usize) [*]const vk.DescriptorSet {
|
||||||
|
return @as([*]const vk.DescriptorSet, @ptrCast(&self.descriptorSets[frameIndex]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, bindingCount: usize, frameCount: usize) !@This() {
|
||||||
|
const self = GpuPipeData{
|
||||||
|
.descriptorSetLayout = undefined,
|
||||||
|
.bindings = try allocator.alloc(GpuPipeDataBinding, bindingCount),
|
||||||
|
.descriptorSets = try allocator.alloc(vk.DescriptorSet, frameCount),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (self.bindings) |*binding| {
|
||||||
|
binding.buffers = try allocator.alloc(NeonVkBuffer, frameCount);
|
||||||
|
binding.frameCount = frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps each buffer per frame
|
||||||
|
pub fn mapBuffers(self: *@This(), gc: *NeonVkContext, comptime ObjectType: type, binding: usize) ![]GpuMappingData(ObjectType) {
|
||||||
|
var pipeDataBuffer = self.bindings[binding];
|
||||||
|
|
||||||
|
return pipeDataBuffer.mapBuffers(gc, ObjectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn unmapAll(self: *@This(), mappings: anytype);
|
||||||
|
pub fn deinit(self: *@This(), allocator: std.mem.Allocator, gc: *NeonVkContext) void {
|
||||||
|
if (self.descriptorSetLayoutIsAllocated) {
|
||||||
|
gc.vkd.destroyDescriptorSetLayout(gc.dev, self.descriptorSetLayout, null);
|
||||||
|
}
|
||||||
|
for (self.bindings) |*binding| {
|
||||||
|
binding.deinit(gc.vkAllocator);
|
||||||
|
allocator.free(binding.buffers);
|
||||||
|
}
|
||||||
|
allocator.free(self.descriptorSets);
|
||||||
|
allocator.free(self.bindings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BindingMode = enum { uniform, storageBuffer };
|
||||||
|
|
||||||
|
pub const GpuPipeDataBuilder = struct {
|
||||||
|
const BindingObjectInfo = struct {
|
||||||
|
objectCount: usize,
|
||||||
|
finalObjectSize: usize,
|
||||||
|
trueObjectSize: usize,
|
||||||
|
bindingMode: BindingMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
gc: *NeonVkContext,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
currentBinding: u32 = 0,
|
||||||
|
frameCount: usize = graphics.constants.NUM_FRAMES,
|
||||||
|
objectCount: usize = graphics.constants.MAX_OBJECTS,
|
||||||
|
bindings: ArrayListUnmanaged(vk.DescriptorSetLayoutBinding) = .{},
|
||||||
|
bindingObjectInfos: ArrayListUnmanaged(BindingObjectInfo) = .{},
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, gc: *NeonVkContext) @This() {
|
||||||
|
const self = GpuPipeDataBuilder{
|
||||||
|
.allocator = allocator,
|
||||||
|
.gc = gc,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setObjectCount(
|
||||||
|
self: *@This(),
|
||||||
|
count: usize,
|
||||||
|
) void {
|
||||||
|
self.objectCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addBufferBinding(
|
||||||
|
self: *@This(),
|
||||||
|
comptime BindingType: type,
|
||||||
|
descriptorType: vk.DescriptorType,
|
||||||
|
stageFlags: vk.ShaderStageFlags,
|
||||||
|
bindingMode: BindingMode,
|
||||||
|
) !void {
|
||||||
|
var gc = self.gc;
|
||||||
|
const binding = vkinit.descriptorSetLayoutBinding(descriptorType, stageFlags, self.currentBinding);
|
||||||
|
|
||||||
|
// core.graphics_log("builder adding additional binding {any} {any} objectSize = {d}", .{ descriptorType, stageFlags, @sizeOf(BindingType) });
|
||||||
|
try self.bindings.append(self.allocator, binding);
|
||||||
|
|
||||||
|
var objCount: usize = 1;
|
||||||
|
|
||||||
|
// todo: there is a bug here because this code is incomplete this only accounts for storage buffers and uniforms
|
||||||
|
if (descriptorType == .storage_buffer) {
|
||||||
|
objCount = self.objectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindingObjectInfo: BindingObjectInfo = .{
|
||||||
|
.objectCount = objCount,
|
||||||
|
.finalObjectSize = @sizeOf(BindingType),
|
||||||
|
.trueObjectSize = @sizeOf(BindingType),
|
||||||
|
.bindingMode = bindingMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
// uniforms require that the buffer object gets padded to the correct size.
|
||||||
|
if (descriptorType != .storage_buffer) {
|
||||||
|
bindingObjectInfo.finalObjectSize = gc.pad_uniform_buffer_size(bindingObjectInfo.finalObjectSize);
|
||||||
|
// core.engine_log("final object size has been padded: {d}", .{bindingObjectInfo.finalObjectSize});
|
||||||
|
} else {
|
||||||
|
var trueSize: usize = 1;
|
||||||
|
while (trueSize < bindingObjectInfo.finalObjectSize) {
|
||||||
|
trueSize *= 2;
|
||||||
|
}
|
||||||
|
bindingObjectInfo.finalObjectSize = trueSize;
|
||||||
|
// core.engine_log("final object size has been padded as storage: {d}", .{bindingObjectInfo.finalObjectSize});
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.bindingObjectInfos.append(self.allocator, bindingObjectInfo);
|
||||||
|
self.currentBinding += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self: *@This(), comptime buildName: []const u8) !GpuPipeData {
|
||||||
|
var rv = try GpuPipeData.init(self.allocator, self.bindings.items.len, self.frameCount);
|
||||||
|
var gc: *NeonVkContext = self.gc;
|
||||||
|
var setInfo = vk.DescriptorSetLayoutCreateInfo{ .binding_count = @as(u32, @intCast(self.bindings.items.len)), .flags = .{}, .p_bindings = self.bindings.items.ptr };
|
||||||
|
rv.descriptorSetLayout = try gc.vkd.createDescriptorSetLayout(gc.dev, &setInfo, null);
|
||||||
|
rv.descriptorSetLayoutIsAllocated = true;
|
||||||
|
// core.graphics_log("finalizing build creating descriptor set layout at 0x{x} buildName: {s}", .{ @intFromEnum(rv.descriptorSetLayout), buildName });
|
||||||
|
|
||||||
|
for (rv.descriptorSets, 0..) |_, frameId| {
|
||||||
|
var descriptorAllocInfo = vk.DescriptorSetAllocateInfo{
|
||||||
|
.descriptor_pool = gc.descriptorPool,
|
||||||
|
.descriptor_set_count = 1,
|
||||||
|
.p_set_layouts = @ptrCast(&rv.descriptorSetLayout),
|
||||||
|
};
|
||||||
|
|
||||||
|
try gc.vkd.allocateDescriptorSets(gc.dev, &descriptorAllocInfo, @as([*]vk.DescriptorSet, @ptrCast(&rv.descriptorSets[frameId])));
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindingId: usize = 0;
|
||||||
|
while (bindingId < self.bindings.items.len) : (bindingId += 1) {
|
||||||
|
const binding = &rv.bindings[bindingId];
|
||||||
|
const bindingInfo: BindingObjectInfo = self.bindingObjectInfos.items[bindingId];
|
||||||
|
|
||||||
|
// core.graphics_log("allocating {d} frame buffers for binding {d} buffer size = {d} object size = {d}", .{ binding.buffers.len, bindingId, bindingInfo.finalObjectSize * bindingInfo.objectCount, bindingInfo.finalObjectSize });
|
||||||
|
|
||||||
|
for (binding.buffers, 0..) |*buffer, frameId| {
|
||||||
|
var usageFlags: vk.BufferUsageFlags = .{};
|
||||||
|
var memoryFlags: vma.MemoryUsage = .unknown;
|
||||||
|
var descriptorType: vk.DescriptorType = .sampler;
|
||||||
|
|
||||||
|
switch (bindingInfo.bindingMode) {
|
||||||
|
.uniform => {
|
||||||
|
usageFlags.uniform_buffer_bit = true;
|
||||||
|
memoryFlags = .cpuToGpu;
|
||||||
|
descriptorType = .uniform_buffer;
|
||||||
|
},
|
||||||
|
.storageBuffer => {
|
||||||
|
usageFlags.storage_buffer_bit = true;
|
||||||
|
memoryFlags = .cpuToGpu;
|
||||||
|
descriptorType = .storage_buffer;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.* = try gc.create_buffer(
|
||||||
|
bindingInfo.finalObjectSize * bindingInfo.objectCount,
|
||||||
|
usageFlags,
|
||||||
|
memoryFlags,
|
||||||
|
"GPU binding buffer creation " ++ @src().fn_name ++ ": " ++ buildName,
|
||||||
|
);
|
||||||
|
|
||||||
|
var bufferInfo = vk.DescriptorBufferInfo{
|
||||||
|
.buffer = buffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.range = bindingInfo.finalObjectSize * bindingInfo.objectCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
var descriptorWrite = vkinit.writeDescriptorSet(
|
||||||
|
descriptorType,
|
||||||
|
rv.descriptorSets[frameId],
|
||||||
|
&bufferInfo,
|
||||||
|
@as(u32, @intCast(bindingId)),
|
||||||
|
);
|
||||||
|
gc.vkd.updateDescriptorSets(gc.dev, 1, @ptrCast(&descriptorWrite), 0, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.bindings.deinit(self.allocator);
|
||||||
|
self.bindingObjectInfos.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
const core = @import("core");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const std = @import("std");
|
||||||
|
const memory = core.MemoryTracker;
|
||||||
|
pub const ozz = @import("ozz");
|
||||||
|
const texture_cooking = @import("cooking/texture_cooking.zig");
|
||||||
|
const mesh_cooking = @import("cooking/mesh_cooking.zig");
|
||||||
|
pub const vk_renderer = @import("vk_renderer.zig");
|
||||||
|
const materials = @import("materials.zig");
|
||||||
|
|
||||||
|
pub usingnamespace @import("debug_draws.zig");
|
||||||
|
pub const gpu_pipe_data = @import("gpu_pipe_data.zig");
|
||||||
|
pub const BoneHandle = animation_system.BoneHandle;
|
||||||
|
|
||||||
|
pub const SkyboxSystem = @import("skybox.zig");
|
||||||
|
pub const setSkybox = SkyboxSystem.setSkybox;
|
||||||
|
|
||||||
|
pub const animation_system = @import("animation/animationSystem.zig");
|
||||||
|
pub const AnimationSystem = animation_system.AnimationSystem;
|
||||||
|
pub const Animator = animation_system.Animator;
|
||||||
|
pub const AnimationTrack = animation_system.AnimationTrack;
|
||||||
|
pub const Skeleton = animation_system.Skeleton;
|
||||||
|
|
||||||
|
pub const animation_resolver = @import("animation/animResolver.zig");
|
||||||
|
pub const AnimResolverRef = animation_resolver.AnimResolverRef;
|
||||||
|
pub const AnimResolverInterface = animation_resolver.AnimResolverInterface;
|
||||||
|
pub const SingleAnimationResolver = animation_resolver.SingleAnimationResolver;
|
||||||
|
pub const BlenderList = animation_resolver.BlenderList;
|
||||||
|
pub const AnimSampler = animation_resolver.AnimSampler;
|
||||||
|
|
||||||
|
const vk_cubemap = @import("vk_renderer/vk_cubemap.zig");
|
||||||
|
pub const CubeMapDirs = vk_cubemap.CubeMapDirs;
|
||||||
|
pub const MakeCubeMapList = vk_cubemap.MakeCubeMapList;
|
||||||
|
|
||||||
|
pub const animation_loaders = @import("animation/loaders.zig");
|
||||||
|
|
||||||
|
pub const RenderThread = @import("vk_renderer/RenderThread.zig");
|
||||||
|
pub const vkinit = @import("vk_init.zig");
|
||||||
|
pub const vk_allocator = @import("vk_allocator.zig");
|
||||||
|
pub const NeonVkAllocator = vk_allocator.NeonVkAllocator;
|
||||||
|
pub const NeonVkPipelineBuilder = vk_renderer.NeonVkPipelineBuilder;
|
||||||
|
pub const NeonVkContext = vk_renderer.NeonVkContext;
|
||||||
|
pub const constants = @import("vk_constants.zig");
|
||||||
|
pub const NeonVkImage = vk_renderer.NeonVkImage;
|
||||||
|
pub const Material = materials.Material;
|
||||||
|
pub const RendererInterfaceRef = vk_renderer.RendererInterfaceRef;
|
||||||
|
pub const RendererInterface = vk_renderer.RendererInterface;
|
||||||
|
pub const texture = @import("texture.zig");
|
||||||
|
pub const debug_draw = @import("debug_draws.zig");
|
||||||
|
pub const mesh = @import("mesh.zig");
|
||||||
|
pub const Mesh = mesh.Mesh;
|
||||||
|
pub const DynamicMesh = mesh.DynamicMesh;
|
||||||
|
pub const IndexBuffer = mesh.IndexBuffer;
|
||||||
|
pub const Texture = texture.Texture;
|
||||||
|
pub const MeshVertex = mesh.MeshVertex;
|
||||||
|
|
||||||
|
pub const mesh_pool = @import("vk_renderer/vk_mesh_pool.zig");
|
||||||
|
pub const MeshSourceType = mesh_pool.MeshSourceType;
|
||||||
|
pub const loadIndexedMeshForPooling = mesh_pool.loadIndexedMeshForPooling;
|
||||||
|
pub const getMeshPoolBuffers = mesh_pool.getMeshPoolBuffers;
|
||||||
|
pub const getIndexedMeshByName = mesh_pool.getIndexedMeshByName;
|
||||||
|
|
||||||
|
// pub const DynamicTexture = @import("dynamic_texture/DynamicTexture.zig");
|
||||||
|
|
||||||
|
pub const vk_util = @import("vk_utils.zig");
|
||||||
|
pub const createAndInstallTextureFromPixels = vk_util.createAndInstallTextureFromPixels;
|
||||||
|
|
||||||
|
const vk_api = @import("../vk_api.zig");
|
||||||
|
pub const vkd = &vk_api.vkd;
|
||||||
|
pub const vki = &vk_api.vki;
|
||||||
|
pub const vkb = &vk_api.vkb;
|
||||||
|
|
||||||
|
pub const PixelBufferRGBA8 = @import("PixelBufferRGBA8.zig");
|
||||||
|
|
||||||
|
pub const vk_assetLoaders = @import("vk_assetLoaders.zig");
|
||||||
|
|
||||||
|
pub const PixelPos = vk_renderer.PixelPos;
|
||||||
|
|
||||||
|
pub const NeonVkBuffer = vk_renderer.NeonVkBuffer;
|
||||||
|
|
||||||
|
pub const NumFrames = constants.NUM_FRAMES;
|
||||||
|
|
||||||
|
const engine_logs = core.engine_logs;
|
||||||
|
const engine_log = core.engine_log;
|
||||||
|
|
||||||
|
pub fn getContext() *NeonVkContext {
|
||||||
|
return vk_renderer.gContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub usingnamespace @import("vk_renderer/vk_renderer_types.zig");
|
||||||
|
|
||||||
|
pub const render_objects = @import("render_objects.zig");
|
||||||
|
pub const Camera = render_objects.Camera;
|
||||||
|
pub const StaticMesh = render_objects.StaticMesh;
|
||||||
|
pub const IndexedMesh = mesh_pool.IndexedMesh;
|
||||||
|
|
||||||
|
pub fn registerRendererPlugin(value: anytype) !void {
|
||||||
|
const ref = RendererInterfaceRef{
|
||||||
|
.ptr = value,
|
||||||
|
.vtable = &@TypeOf(value.*).RendererInterfaceVTable,
|
||||||
|
};
|
||||||
|
var gc = getContext();
|
||||||
|
try gc.rendererPlugins.append(gc.allocator, ref);
|
||||||
|
}
|
||||||
|
var gCooking: bool = false;
|
||||||
|
|
||||||
|
const primitives = [_]assets.AssetImportReference{
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_sphere", "meshes/primitive_sphere.obj"),
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_box", "meshes/primitive_box.obj"),
|
||||||
|
assets.MakeImportRef("Mesh", "m_primitive_line", "meshes/primitive_line.obj"),
|
||||||
|
assets.MakeImportRef("Mesh", "m_skybox", "meshes/skybox.obj"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void {
|
||||||
|
_ = args;
|
||||||
|
if (!core.isUtility()) {
|
||||||
|
engine_logs("graphics module starting up...");
|
||||||
|
|
||||||
|
const context: *NeonVkContext = core.gEngine.createObject(
|
||||||
|
NeonVkContext,
|
||||||
|
.{ .can_tick = true, .isCore = true },
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
vk_renderer.gContext = context;
|
||||||
|
|
||||||
|
const as = try core.createObject(AnimationSystem, .{ .can_tick = false });
|
||||||
|
try animation_loaders.initLoaders();
|
||||||
|
|
||||||
|
try registerRendererPlugin(as);
|
||||||
|
|
||||||
|
vk_assetLoaders.init_loaders(allocator) catch unreachable;
|
||||||
|
|
||||||
|
try assets.loadList(primitives);
|
||||||
|
debug_draw.init_debug_draw_subsystem() catch unreachable;
|
||||||
|
|
||||||
|
context.skybox = SkyboxSystem.create(context.allocator) catch return core.EngineDataEventError.UnknownStatePanic;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasField(@TypeOf(programSpec), "cooking")) {
|
||||||
|
gCooking = true;
|
||||||
|
try texture_cooking.initCooker(allocator);
|
||||||
|
try mesh_cooking.initCooker(allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_module(allocator: std.mem.Allocator) void {
|
||||||
|
_ = allocator;
|
||||||
|
if (gCooking) {
|
||||||
|
mesh_cooking.deinitCooker();
|
||||||
|
texture_cooking.deinitCooker();
|
||||||
|
}
|
||||||
|
if (!core.isUtility()) {
|
||||||
|
engine_logs("graphics module shutting down...");
|
||||||
|
vk_renderer.gContext.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub var icon: []const u8 = "content/textures/icon.png";
|
||||||
|
|
||||||
|
pub fn setStartupSettings(comptime field: []const u8, value: anytype) void {
|
||||||
|
@field(vk_renderer.gGraphicsStartupSettings, field) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getStartupSettings() *const @TypeOf(vk_renderer.gGraphicsStartupSettings) {
|
||||||
|
return &vk_renderer.gGraphicsStartupSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadSpv(allocator: std.mem.Allocator, path: []const u8) ![]const u32 {
|
||||||
|
core.engine_log("loading path {s}", .{path});
|
||||||
|
const search_prefixes: []const []const u8 = &.{
|
||||||
|
"zig-out/shaders",
|
||||||
|
"shaders",
|
||||||
|
};
|
||||||
|
|
||||||
|
var s_path: [4096]u8 = undefined;
|
||||||
|
|
||||||
|
for (search_prefixes) |prefix| {
|
||||||
|
const s = try std.fmt.bufPrint(&s_path, "{s}/{s}", .{ prefix, path });
|
||||||
|
var file = std.fs.cwd().openFile(s, .{ .mode = .read_only }) catch continue;
|
||||||
|
const filesize = (try file.stat()).size;
|
||||||
|
const buffer: []u8 = try allocator.alignedAlloc(u8, 4, filesize);
|
||||||
|
try file.reader().readNoEof(buffer);
|
||||||
|
|
||||||
|
var rv: []u32 = undefined;
|
||||||
|
rv.ptr = @as([*]u32, @ptrCast(@alignCast(buffer.ptr)));
|
||||||
|
rv.len = buffer.len / 4;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.FileNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Module = core.ModuleDescription{
|
||||||
|
.name = "graphics",
|
||||||
|
.enabledByDefault = true,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const core = @import("core");
|
||||||
|
const VkConstants = @import("vk_constants.zig");
|
||||||
|
const meshes = @import("mesh.zig");
|
||||||
|
const NeonVkContext = @import("vk_renderer.zig").NeonVkContext;
|
||||||
|
const vk_pipeline = @import("vk_pipeline.zig");
|
||||||
|
|
||||||
|
const NeonVkPipelineBuilder = vk_pipeline.NeonVkPipelineBuilder;
|
||||||
|
const EulerAngles = core.EulerAngles;
|
||||||
|
const Mat = core.Mat;
|
||||||
|
const Vectorf = core.Vectorf;
|
||||||
|
const Quat = core.Quat;
|
||||||
|
const zm = core.zm;
|
||||||
|
const mul = zm.mul;
|
||||||
|
|
||||||
|
pub const Material = struct {
|
||||||
|
materialName: core.Name,
|
||||||
|
textureSet: vk.DescriptorSet = .null_handle,
|
||||||
|
pipeline: vk.Pipeline,
|
||||||
|
layout: vk.PipelineLayout,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Material, ctx: *NeonVkContext) void {
|
||||||
|
ctx.vkd.destroyPipeline(ctx.dev, self.pipeline, null);
|
||||||
|
ctx.vkAllocator.destroyPipelineLayout(ctx.dev, self.layout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MaterialBuilder = struct {
|
||||||
|
const Self = @This();
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
ctx: *NeonVkContext,
|
||||||
|
pipelineBuilder: NeonVkPipelineBuilder,
|
||||||
|
|
||||||
|
pub fn init(ctx: *NeonVkContext) MaterialBuilder {
|
||||||
|
const self = MaterialBuilder{
|
||||||
|
.allocator = ctx.allocator,
|
||||||
|
.ctx = ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self: *Self) !void {
|
||||||
|
_ = self;
|
||||||
|
// try self.ctx.add_material();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const vk_renderer = @import("vk_renderer.zig");
|
||||||
|
const vma = @import("vma");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const obj_loader = @import("objLoader");
|
||||||
|
const constants = @import("vk_constants.zig");
|
||||||
|
const vk_utils = @import("vk_utils.zig");
|
||||||
|
|
||||||
|
const vk_dynamic_mesh = @import("vk_dynamic_mesh.zig");
|
||||||
|
|
||||||
|
const NeonVkUploader = vk_utils.NeonVkUploader;
|
||||||
|
const NeonVkBuffer = vk_renderer.NeonVkBuffer;
|
||||||
|
const ObjMesh = obj_loader.ObjMesh;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
const Vectorf = core.Vectorf;
|
||||||
|
const Vector2f = core.Vector2f;
|
||||||
|
const Color = core.colors.Color;
|
||||||
|
const NeonVkContext = vk_renderer.NeonVkContext;
|
||||||
|
|
||||||
|
const debug_struct = core.debug_struct;
|
||||||
|
|
||||||
|
pub const DynamicMesh = vk_dynamic_mesh.DynamicMesh;
|
||||||
|
pub const DynamicMeshManager = vk_dynamic_mesh.DynamicMeshManager;
|
||||||
|
|
||||||
|
pub const MeshVertex = extern struct {
|
||||||
|
position: Vectorf = .{},
|
||||||
|
normal: Vectorf = .{},
|
||||||
|
color: Color = .{},
|
||||||
|
uv: Vector2f = .{},
|
||||||
|
bones: [4]u8 = .{ 0, 0, 0, 0 },
|
||||||
|
weights: [4]u8 = .{ 0, 0, 0, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const IndexBuffer = struct {
|
||||||
|
buffer: NeonVkBuffer,
|
||||||
|
indices: []const u32,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn uploadIndexBuffer(gc: *NeonVkContext, indices: []const u32, allocator: std.mem.Allocator) !@This() {
|
||||||
|
var self = @This(){
|
||||||
|
.buffer = undefined,
|
||||||
|
.indices = indices,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.indices = try allocator.dupe(u32, indices);
|
||||||
|
|
||||||
|
const bufferSize = indices.len * @sizeOf(u32);
|
||||||
|
|
||||||
|
const bci = vk.BufferCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.size = bufferSize,
|
||||||
|
.usage = .{ .transfer_src_bit = true },
|
||||||
|
.sharing_mode = .exclusive,
|
||||||
|
.queue_family_index_count = 0,
|
||||||
|
.p_queue_family_indices = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vmaCreateInfo = vma.AllocationCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.usage = .cpuOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
var stagingBuffer = try gc.vkAllocator.createBuffer(bci, vmaCreateInfo, @src().fn_name ++ " - upload buffer");
|
||||||
|
defer stagingBuffer.deinit(gc.vkAllocator);
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = try gc.vkAllocator.vmaAllocator.mapMemory(stagingBuffer.allocation, u8);
|
||||||
|
var dataSlice: []u8 = undefined;
|
||||||
|
dataSlice.ptr = data;
|
||||||
|
dataSlice.len = bufferSize;
|
||||||
|
|
||||||
|
var iSlice: []const u8 = undefined;
|
||||||
|
iSlice.ptr = @as([*]const u8, @ptrCast(indices.ptr));
|
||||||
|
iSlice.len = bufferSize;
|
||||||
|
|
||||||
|
@memcpy(dataSlice, iSlice);
|
||||||
|
gc.vkAllocator.vmaAllocator.unmapMemory(stagingBuffer.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU sided buffer
|
||||||
|
|
||||||
|
const gpuBci = vk.BufferCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.size = bufferSize,
|
||||||
|
.usage = .{ .transfer_dst_bit = true, .index_buffer_bit = true },
|
||||||
|
.sharing_mode = .exclusive,
|
||||||
|
.queue_family_index_count = 0,
|
||||||
|
.p_queue_family_indices = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const gpuVmaCreateInfo = vma.AllocationCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.usage = .gpuOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.buffer = try gc.vkAllocator.createBuffer(gpuBci, gpuVmaCreateInfo, @src().fn_name ++ " - gpu buffer");
|
||||||
|
//try gc.start_upload_context(&gc.uploadContext);
|
||||||
|
try gc.uploader.startUploadContext();
|
||||||
|
{
|
||||||
|
var copy = vk.BufferCopy{
|
||||||
|
.dst_offset = 0,
|
||||||
|
.src_offset = 0,
|
||||||
|
.size = bufferSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmd = gc.uploader.commandBuffer;
|
||||||
|
// core.graphics_log("Starting command copy buffer", .{});
|
||||||
|
|
||||||
|
gc.vkd.cmdCopyBuffer(
|
||||||
|
cmd,
|
||||||
|
stagingBuffer.buffer,
|
||||||
|
self.buffer.buffer,
|
||||||
|
1,
|
||||||
|
@as([*]const vk.BufferCopy, @ptrCast(©)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//try gc.finish_upload_context(&gc.uploadContext);
|
||||||
|
try gc.uploader.finishUploadContext();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), gc: *NeonVkContext) void {
|
||||||
|
self.buffer.deinit(gc.vkAllocator);
|
||||||
|
self.allocator.free(self.indices);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn loadObjMeshVertices(vertices: *ArrayList(MeshVertex), mesh: ObjMesh) !void {
|
||||||
|
try mesh.validate_mesh();
|
||||||
|
|
||||||
|
try vertices.ensureTotalCapacity(mesh.v_faces.items.len * 3);
|
||||||
|
|
||||||
|
for (mesh.v_faces.items) |face| {
|
||||||
|
if (face.count == 3) {
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < 3) : (i += 1) {
|
||||||
|
const v = vertexFromFaceOffset(mesh, face, i);
|
||||||
|
try vertices.append(v);
|
||||||
|
}
|
||||||
|
} else if (face.count == 4) {
|
||||||
|
const vx = [_]MeshVertex{
|
||||||
|
vertexFromFaceOffset(mesh, face, 0),
|
||||||
|
vertexFromFaceOffset(mesh, face, 1),
|
||||||
|
vertexFromFaceOffset(mesh, face, 2),
|
||||||
|
vertexFromFaceOffset(mesh, face, 2),
|
||||||
|
vertexFromFaceOffset(mesh, face, 3),
|
||||||
|
vertexFromFaceOffset(mesh, face, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
try vertices.appendSlice(vx[0..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertexFromFaceOffset(mesh: ObjMesh, face: obj_loader.ObjFace, offset: u32) MeshVertex {
|
||||||
|
const p = mesh.v_positions.items[face.vertex[offset] - 1];
|
||||||
|
const n = mesh.v_normals.items[face.normal[offset] - 1];
|
||||||
|
const u = mesh.v_uvs.items[face.texture[offset] - 1];
|
||||||
|
const v = MeshVertex{
|
||||||
|
.position = .{ .x = p.x, .y = p.y, .z = p.z },
|
||||||
|
.normal = .{ .x = n.x, .y = n.y, .z = n.z },
|
||||||
|
.color = .{ .r = n.x, .g = n.y, .b = n.z, .a = 1.0 },
|
||||||
|
.uv = .{ .x = u.x, .y = 1 - u.y },
|
||||||
|
};
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy, don't use
|
||||||
|
pub const Mesh = struct {
|
||||||
|
vertices: ArrayList(MeshVertex),
|
||||||
|
buffer: NeonVkBuffer,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(context: *NeonVkContext, allocator: std.mem.Allocator) Mesh {
|
||||||
|
const self = Mesh{
|
||||||
|
.vertices = ArrayList(MeshVertex).init(allocator),
|
||||||
|
.buffer = undefined,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
_ = context;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload(self: *Mesh, ctx: *NeonVkContext) !void {
|
||||||
|
try ctx.stage_and_push_mesh(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadFromObjFileCooked(self: *Mesh, fileName: []const u8) !void {
|
||||||
|
const mapping = try core.fs().loadFile(fileName);
|
||||||
|
defer core.fs().unmap(mapping);
|
||||||
|
const s = @sizeOf(MeshVertex);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
var vertexOffset: usize = 0;
|
||||||
|
try self.vertices.resize(1 + mapping.bytes.len / s);
|
||||||
|
while (i < mapping.bytes.len) : (i += s) {
|
||||||
|
self.vertices.items[vertexOffset] = @as(*const MeshVertex, @ptrCast(@alignCast(mapping.bytes.ptr + i))).*;
|
||||||
|
vertexOffset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_from_obj_file(self: *Mesh, fileName: []const u8) !void {
|
||||||
|
const mapping = try core.fs().loadFile(fileName);
|
||||||
|
defer core.fs().unmap(mapping);
|
||||||
|
var fileObjs = try obj_loader.loadObjBytes(mapping.bytes, self.allocator);
|
||||||
|
defer fileObjs.deinit();
|
||||||
|
|
||||||
|
if (fileObjs.meshes.items.len > 0) {
|
||||||
|
// default grabbing shape zero
|
||||||
|
core.graphics_log("loading mesh: {s}", .{fileName});
|
||||||
|
// fileObjs.meshes.items[0].print_stats();
|
||||||
|
// try self.load_from_obj_mesh(fileObjs.meshes.items[0]);
|
||||||
|
try loadObjMeshVertices(&self.vertices, fileObjs.meshes.items[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.graphics_log("mesh loaded with {d} vertices size {d}", .{ self.vertices.items.len, self.vertices.items.len * @sizeOf(MeshVertex) });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Mesh, ctx: *NeonVkContext) void {
|
||||||
|
self.buffer.deinit(ctx.vkAllocator);
|
||||||
|
self.vertices.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VertexInputDescription = struct {
|
||||||
|
bindings: ArrayList(vk.VertexInputBindingDescription),
|
||||||
|
attributes: ArrayList(vk.VertexInputAttributeDescription),
|
||||||
|
flags: vk.PipelineVertexInputStateCreateFlags = .{},
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !VertexInputDescription {
|
||||||
|
var self = VertexInputDescription{
|
||||||
|
.bindings = ArrayList(vk.VertexInputBindingDescription).init(allocator),
|
||||||
|
.attributes = ArrayList(vk.VertexInputAttributeDescription).init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.bindings.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.stride = @sizeOf(MeshVertex),
|
||||||
|
.input_rate = .vertex,
|
||||||
|
});
|
||||||
|
|
||||||
|
//debug_struct("bindings 0", self.bindings.items[0]);
|
||||||
|
|
||||||
|
// position
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 0,
|
||||||
|
.format = .r32g32b32_sfloat,
|
||||||
|
.offset = @offsetOf(MeshVertex, "position"),
|
||||||
|
});
|
||||||
|
//debug_struct("attributes 0", self.attributes.items[0]);
|
||||||
|
|
||||||
|
// normal
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 1,
|
||||||
|
.format = .r32g32b32_sfloat,
|
||||||
|
.offset = @offsetOf(MeshVertex, "normal"),
|
||||||
|
});
|
||||||
|
|
||||||
|
//debug_struct("attributes 0", self.attributes.items[1]);
|
||||||
|
// color
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 2,
|
||||||
|
.format = .r32g32b32a32_sfloat,
|
||||||
|
.offset = @offsetOf(MeshVertex, "color"),
|
||||||
|
});
|
||||||
|
|
||||||
|
//debug_struct("attributes 0", self.attributes.items[2]);
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 3,
|
||||||
|
.format = .r32g32_sfloat,
|
||||||
|
.offset = @offsetOf(MeshVertex, "uv"),
|
||||||
|
});
|
||||||
|
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 4,
|
||||||
|
.format = .a8b8g8r8_uint_pack32,
|
||||||
|
.offset = @offsetOf(MeshVertex, "bones"),
|
||||||
|
});
|
||||||
|
|
||||||
|
try self.attributes.append(.{
|
||||||
|
.binding = 0,
|
||||||
|
.location = 5,
|
||||||
|
.format = .a8b8g8r8_uint_pack32,
|
||||||
|
.offset = @offsetOf(MeshVertex, "weights"),
|
||||||
|
});
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.bindings.deinit();
|
||||||
|
self.attributes.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const resources = @import("resources");
|
||||||
|
const core = @import("core");
|
||||||
|
const VkConstants = @import("vk_constants.zig");
|
||||||
|
const graphics = @import("graphics.zig");
|
||||||
|
const meshes = @import("mesh.zig");
|
||||||
|
const NeonVkContext = @import("vk_renderer.zig").NeonVkContext;
|
||||||
|
const materials = @import("materials.zig");
|
||||||
|
const animationSystem = @import("animation/animationSystem.zig");
|
||||||
|
const Animator = animationSystem.Animator;
|
||||||
|
|
||||||
|
const Material = materials.Material;
|
||||||
|
const EulerAngles = core.EulerAngles;
|
||||||
|
const Mat = core.Mat;
|
||||||
|
const Vectorf = core.Vectorf;
|
||||||
|
const Quat = core.Quat;
|
||||||
|
const zm = core.zm;
|
||||||
|
const mul = zm.mul;
|
||||||
|
|
||||||
|
const Mesh = meshes.Mesh;
|
||||||
|
const mesh_pool = @import("vk_renderer/vk_mesh_pool.zig");
|
||||||
|
const IndexedMesh = mesh_pool.IndexedMesh;
|
||||||
|
|
||||||
|
// lol we need to rename this thing again, it should be called RenderMesh
|
||||||
|
pub const StaticMeshSet = core.SparseSet(StaticMesh);
|
||||||
|
|
||||||
|
pub const StaticMesh = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
mesh: ?IndexedMesh = null,
|
||||||
|
// texture: ?vk.DescriptorSet = null,
|
||||||
|
textureId: ?u32 = null,
|
||||||
|
transform: core.Mat = core.zm.translation(0, 0, 0),
|
||||||
|
visibility: bool = true,
|
||||||
|
|
||||||
|
// new position and rotator based api
|
||||||
|
position: Vectorf = .{},
|
||||||
|
rotation: Quat = .{ 0, 0, 0, 1 },
|
||||||
|
scale: Vectorf = .{ .x = 1, .y = 1, .z = 1 },
|
||||||
|
|
||||||
|
textureName: core.Name = core.NameInvalid,
|
||||||
|
meshName: core.Name = core.NameInvalid,
|
||||||
|
|
||||||
|
animated: bool = false, // todo remove
|
||||||
|
animator: ?*Animator = null,
|
||||||
|
|
||||||
|
flags: Flags0 = .{},
|
||||||
|
|
||||||
|
pub var BaseContainer: *StaticMeshSet = undefined;
|
||||||
|
pub const ComponentName = "StaticMesh";
|
||||||
|
|
||||||
|
pub const ScriptExports: []const []const u8 = &.{
|
||||||
|
"applyRelativeRotationX",
|
||||||
|
"applyRelativeRotationY",
|
||||||
|
"applyRelativeRotationZ",
|
||||||
|
"setMesh",
|
||||||
|
"setTextureByName",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Flags0 = packed struct(u32) {
|
||||||
|
alwaysInFront: bool = false,
|
||||||
|
useAltFov: bool = false,
|
||||||
|
_pad: u30 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn setMeshByName(self: *@This(), meshName: core.Name) void {
|
||||||
|
self.meshName = meshName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// script function
|
||||||
|
pub fn setMesh(self: *@This(), meshName: []const u8) void {
|
||||||
|
const name = core.MakeName(meshName);
|
||||||
|
self.mesh = graphics.getIndexedMeshByName(core.MakeName(meshName));
|
||||||
|
self.meshName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromTransform(transform: core.Mat) Self {
|
||||||
|
var self = Self{
|
||||||
|
.mesh = null,
|
||||||
|
.transform = transform,
|
||||||
|
.position = undefined,
|
||||||
|
.rotation = undefined,
|
||||||
|
.scale = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.updateScalars();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTexture(self: *Self, textureName: []const u8) void {
|
||||||
|
var name = core.MakeName(textureName);
|
||||||
|
self.textureId = graphics.getContext().textureIds.get(name.handle());
|
||||||
|
self.textureName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTextureByName(self: *Self, _name: core.Name) void {
|
||||||
|
var name = _name;
|
||||||
|
self.textureId = graphics.getContext().textureIds.get(name.handle());
|
||||||
|
self.textureName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateTexture(self: *@This(), gc: *NeonVkContext) void {
|
||||||
|
self.textureId = gc.textureIds.get(self.textureName.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyTransform(self: *StaticMesh, transform: core.Mat) void {
|
||||||
|
self.transform = core.zm.mul(self.transform, transform);
|
||||||
|
self.updateScalars();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyRelativeRotationX(self: *StaticMesh, angle: f32) void {
|
||||||
|
var imat = core.zm.identity();
|
||||||
|
imat[0][3] = -self.transform[0][3];
|
||||||
|
imat[1][3] = -self.transform[1][3];
|
||||||
|
imat[2][3] = -self.transform[2][3];
|
||||||
|
|
||||||
|
const rmat = core.zm.identity();
|
||||||
|
imat[0][3] = self.transform[0][3];
|
||||||
|
imat[1][3] = self.transform[1][3];
|
||||||
|
imat[2][3] = self.transform[2][3];
|
||||||
|
|
||||||
|
var newTransform = core.zm.mul(imat, self.transform);
|
||||||
|
newTransform = core.zm.mul(core.zm.rotationX(angle), newTransform);
|
||||||
|
newTransform = core.zm.mul(rmat, newTransform);
|
||||||
|
self.transform = newTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyRelativeRotationZ(self: *StaticMesh, angle: f32) void {
|
||||||
|
var imat = core.zm.identity();
|
||||||
|
imat[0][3] = -self.transform[0][3];
|
||||||
|
imat[1][3] = -self.transform[1][3];
|
||||||
|
imat[2][3] = -self.transform[2][3];
|
||||||
|
|
||||||
|
const rmat = core.zm.identity();
|
||||||
|
imat[0][3] = self.transform[0][3];
|
||||||
|
imat[1][3] = self.transform[1][3];
|
||||||
|
imat[2][3] = self.transform[2][3];
|
||||||
|
|
||||||
|
var newTransform = core.zm.mul(imat, self.transform);
|
||||||
|
newTransform = core.zm.mul(core.zm.rotationZ(angle), newTransform);
|
||||||
|
newTransform = core.zm.mul(rmat, newTransform);
|
||||||
|
self.transform = newTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyRelativeRotationY(self: *StaticMesh, angle: f32) void {
|
||||||
|
var imat = core.zm.identity();
|
||||||
|
imat[0][3] = -self.transform[0][3];
|
||||||
|
imat[1][3] = -self.transform[1][3];
|
||||||
|
imat[2][3] = -self.transform[2][3];
|
||||||
|
|
||||||
|
const rmat = core.zm.identity();
|
||||||
|
imat[0][3] = self.transform[0][3];
|
||||||
|
imat[1][3] = self.transform[1][3];
|
||||||
|
imat[2][3] = self.transform[2][3];
|
||||||
|
|
||||||
|
var newTransform = core.zm.mul(imat, self.transform);
|
||||||
|
newTransform = core.zm.mul(core.zm.rotationY(angle), newTransform);
|
||||||
|
newTransform = core.zm.mul(rmat, newTransform);
|
||||||
|
self.transform = newTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateScalars(self: *StaticMesh) void {
|
||||||
|
self.position = Vectorf.fromZm(mul(self.transform, Vectorf.new(0.0, 0.0, 0.0).toZm()));
|
||||||
|
self.rotation = zm.matToQuat(self.transform);
|
||||||
|
self.scale = core.matToScalef(self.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyScalars(self: *StaticMesh) void {
|
||||||
|
var newTransform = core.zm.mul(
|
||||||
|
core.zm.scalingV(self.scale.toZm()),
|
||||||
|
core.zm.matFromQuat(self.rotation),
|
||||||
|
);
|
||||||
|
newTransform = core.zm.mul(
|
||||||
|
newTransform,
|
||||||
|
core.zm.translationV(self.position.toZm()),
|
||||||
|
);
|
||||||
|
self.transform = newTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initECS(self: *@This(), handle: core.ObjectHandle) void {
|
||||||
|
const entity = core.Entity.fromHandle(handle);
|
||||||
|
if (entity.get(core.Scene)) |scene| {
|
||||||
|
self.position = scene.getPosition();
|
||||||
|
self.rotation = scene.getRotation().quat;
|
||||||
|
self.scale = scene.getScaleV();
|
||||||
|
} else {
|
||||||
|
_ = entity.addComponent(core.Scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn makePerspective(fov: f32, aspect: f32, near: f32, far: f32) Mat {
|
||||||
|
const proj = core.zm.perspectiveFovRh(
|
||||||
|
core.radians(fov),
|
||||||
|
aspect,
|
||||||
|
near,
|
||||||
|
far,
|
||||||
|
);
|
||||||
|
// proj[1][1] *= -1;
|
||||||
|
return proj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera coordinate system:
|
||||||
|
//
|
||||||
|
// from you as a user, staring at the screen:
|
||||||
|
//
|
||||||
|
// This is a right handed coordinate system
|
||||||
|
//
|
||||||
|
// forward = +Z (index finger)
|
||||||
|
// left = +X (middle finger)
|
||||||
|
// up = +Y (thumb)
|
||||||
|
|
||||||
|
const ecs = core.ecs;
|
||||||
|
|
||||||
|
pub const Camera = struct {
|
||||||
|
fov: f32 = 70.0,
|
||||||
|
altFov: f32 = 70.0,
|
||||||
|
aspect: f32 = 16.0 / 9.0,
|
||||||
|
near_clipping: f32 = 0.1,
|
||||||
|
far_clipping: f32 = 10000.0,
|
||||||
|
|
||||||
|
position: Vectorf = Vectorf{ .x = 0.0, .y = 0.0, .z = 0.0 },
|
||||||
|
rotation: Quat, // todo, remove, we only work with euler tracks now for camera.
|
||||||
|
|
||||||
|
// applied in that order,
|
||||||
|
yaw: f32 = 0,
|
||||||
|
pitch: f32 = 0,
|
||||||
|
roll: f32 = 0,
|
||||||
|
|
||||||
|
transform: core.Transform = zm.identity(),
|
||||||
|
worldTransform: Mat = zm.identity(),
|
||||||
|
projection: Mat = makePerspective(
|
||||||
|
core.radians(70.0), // angle
|
||||||
|
16.0 / 9.0,
|
||||||
|
0.1,
|
||||||
|
200000,
|
||||||
|
),
|
||||||
|
projectionAlt: Mat = makePerspective(
|
||||||
|
core.radians(70.0), // angle
|
||||||
|
16.0 / 9.0,
|
||||||
|
0.0001,
|
||||||
|
1000,
|
||||||
|
),
|
||||||
|
|
||||||
|
final: Mat = zm.identity(),
|
||||||
|
finalAlt: Mat = zm.identity(),
|
||||||
|
|
||||||
|
pub const EcsComponentDefinition = ecs.DefineComponent(@This(), .set); // set, map, multiset, maplist
|
||||||
|
|
||||||
|
pub fn init() Camera {
|
||||||
|
return .{
|
||||||
|
.rotation = zm.quatFromRollPitchYaw(0.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(self: *Camera, offset: core.Vectorf) void {
|
||||||
|
var off: core.Vectorf = offset;
|
||||||
|
off.y = offset.y;
|
||||||
|
off.x = offset.x;
|
||||||
|
off.z = offset.z;
|
||||||
|
self.*.position = self.position.add(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRotation(self: *Camera) Quat {
|
||||||
|
return zm.quatFromMat(self.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRotationEuler(self: *@This(), x: f32, y: f32, z: f32) void {
|
||||||
|
self.rotation = core.zm.quatFromRollPitchYaw(x, y + core.radians(180.0), z);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateCamera(self: *Camera) void {
|
||||||
|
self.projection = zm.perspectiveFovRh(core.radians(self.fov), 16.0 / 9.0, 0.1, 200000);
|
||||||
|
self.projection[1][1] *= -1;
|
||||||
|
|
||||||
|
self.projectionAlt = zm.perspectiveFovRh(core.radians(self.altFov), 16.0 / 9.0, 0.01, 200000);
|
||||||
|
self.projectionAlt[1][1] *= -1;
|
||||||
|
|
||||||
|
// self.projectionAlt = self.projection;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(self: *Camera) void {
|
||||||
|
{
|
||||||
|
var base = core.zm.identity();
|
||||||
|
base = mul(core.zm.rotationY(-self.yaw), base);
|
||||||
|
base = mul(core.zm.rotationX(self.pitch), base);
|
||||||
|
base = mul(core.zm.rotationZ(self.roll), base);
|
||||||
|
|
||||||
|
const pr2 = core.scene.SceneObjectPosRot{
|
||||||
|
.position = self.position,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.worldTransform = mul(base, pr2.toTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate viewProjections
|
||||||
|
{
|
||||||
|
var base = core.zm.rotationY(self.yaw + core.radians(180.0));
|
||||||
|
base = mul(base, core.zm.rotationX(self.pitch));
|
||||||
|
base = mul(base, core.zm.rotationZ(self.roll));
|
||||||
|
self.transform = base;
|
||||||
|
// self.transform = mul(
|
||||||
|
// base,
|
||||||
|
// mul(zm.matFromQuat(self.rotation), zm.rotationY(core.radians(180.0))),
|
||||||
|
// );
|
||||||
|
var position = self.position;
|
||||||
|
// position.x *= -1;
|
||||||
|
// position.z *= -1;
|
||||||
|
self.transform = mul(zm.translationV(position.fmul(-1).toZm()), self.transform);
|
||||||
|
self.final = mul(self.transform, self.projection);
|
||||||
|
self.finalAlt = mul(self.transform, self.projectionAlt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cubeMapShared: [graphics.NumFrames]?vk.DescriptorSet = .{ null, null },
|
||||||
|
cubeMapTextureSet: ?vk.DescriptorSet = null,
|
||||||
|
cubeMapName: ?core.Name = null,
|
||||||
|
material: *graphics.Material = undefined,
|
||||||
|
mesh: ?graphics.IndexedMesh = null,
|
||||||
|
|
||||||
|
pub const RendererInterfaceVTable = graphics.RendererInterface.from(@This());
|
||||||
|
|
||||||
|
pub var gSkybox: *@This() = undefined;
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
gSkybox = self;
|
||||||
|
try self.initPipeline();
|
||||||
|
try graphics.registerRendererPlugin(self);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendShared(self: *@This(), fi: u32) void {
|
||||||
|
if (self.cubeMapName == null) {
|
||||||
|
self.cubeMapShared[fi] = self.cubeMapTextureSet;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (self.mesh == null)
|
||||||
|
self.mesh = graphics.getIndexedMeshByName(core.MakeName("m_skybox"));
|
||||||
|
|
||||||
|
if (self.cubeMapTextureSet == null) {
|
||||||
|
const handle = self.cubeMapName.?.handle();
|
||||||
|
self.cubeMapTextureSet = graphics.getContext().textureSets.get(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cubeMapShared[fi] = self.cubeMapTextureSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initPipeline(self: *@This()) !void {
|
||||||
|
const gc = graphics.getContext();
|
||||||
|
|
||||||
|
var pipelineBuilder = try graphics.NeonVkPipelineBuilder.init(
|
||||||
|
gc.dev,
|
||||||
|
gc.vkd,
|
||||||
|
self.allocator,
|
||||||
|
gc.vkAllocator,
|
||||||
|
skybox_vert.spv(),
|
||||||
|
skybox_frag.spv(),
|
||||||
|
);
|
||||||
|
defer pipelineBuilder.deinit();
|
||||||
|
|
||||||
|
try pipelineBuilder.add_mesh_description();
|
||||||
|
try pipelineBuilder.add_layout(gc.globalDescriptorLayout);
|
||||||
|
try pipelineBuilder.add_layout(gc.singleTextureSetLayout);
|
||||||
|
try pipelineBuilder.add_depth_stencil(); // todo.. we might not want this for a skybox.
|
||||||
|
try pipelineBuilder.init_triangle_pipeline(gc.actual_extent);
|
||||||
|
pipelineBuilder.pdsci.?.depth_write_enable = vk.FALSE;
|
||||||
|
pipelineBuilder.pdsci.?.depth_test_enable = vk.FALSE;
|
||||||
|
pipelineBuilder.pdsci.?.depth_compare_op = .never;
|
||||||
|
|
||||||
|
const materialName = core.MakeName("Mat_skybox");
|
||||||
|
self.material = try self.allocator.create(graphics.Material);
|
||||||
|
self.material.* = graphics.Material{
|
||||||
|
.materialName = materialName,
|
||||||
|
.pipeline = (try pipelineBuilder.build(gc.renderPass)).?,
|
||||||
|
.layout = pipelineBuilder.pipelineLayout,
|
||||||
|
};
|
||||||
|
|
||||||
|
try gc.add_material(self.material);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSkybox(textureName: []const u8) !void {
|
||||||
|
const name = core.MakeName(textureName);
|
||||||
|
gSkybox.cubeMapName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vk_renderer_interface = @import("vk_renderer/vk_renderer_interface.zig");
|
||||||
|
const RendererInterface = vk_renderer_interface.RendererInterface;
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const graphics = @import("graphics.zig");
|
||||||
|
const core = @import("core");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const skybox_vert = @import("skybox_vert");
|
||||||
|
const skybox_frag = @import("skybox_frag");
|
||||||
|
|
||||||
|
const vkinit = @import("vk_init.zig");
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core");
|
||||||
|
const vk_renderer = @import("vk_renderer.zig");
|
||||||
|
const vma = @import("vma");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const vkinit = @import("vk_init.zig");
|
||||||
|
|
||||||
|
const NeonVkContext = vk_renderer.NeonVkContext;
|
||||||
|
const NeonVkBuffer = vk_renderer.NeonVkBuffer;
|
||||||
|
const NeonVkImage = vk_renderer.NeonVkImage;
|
||||||
|
|
||||||
|
pub const PixelPos = struct {
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
|
||||||
|
/// returns y/x of the pixel position
|
||||||
|
pub fn ratio(self: @This()) f32 {
|
||||||
|
return @as(f32, @floatFromInt(self.y)) / @as(f32, @floatFromInt(self.x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is a simple display texture
|
||||||
|
pub const Texture = struct {
|
||||||
|
image: NeonVkImage,
|
||||||
|
imageView: vk.ImageView,
|
||||||
|
isCube: bool = false,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), ctx: *NeonVkContext) void {
|
||||||
|
ctx.vkd.destroyImageView(ctx.dev, self.imageView, null);
|
||||||
|
self.image.deinit(ctx.vkAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDimensions(self: @This()) PixelPos {
|
||||||
|
return .{
|
||||||
|
.x = self.image.pixelWidth,
|
||||||
|
.y = self.image.pixelHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,382 @@
|
||||||
|
// simple wrapper around vma with a very slow debug mode that
|
||||||
|
// shows every single vma event
|
||||||
|
//
|
||||||
|
// BECAUSE I CAN'T FIND WHERE I FAILED TO DESTROY SOME MEMORY.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const vma = @import("vma");
|
||||||
|
const core = @import("core");
|
||||||
|
const memory = core.MemoryTracker;
|
||||||
|
const vk_constants = @import("vk_constants.zig");
|
||||||
|
|
||||||
|
const DeviceDispatch = vk_constants.DeviceDispatch;
|
||||||
|
const BaseDispatch = vk_constants.BaseDispatch;
|
||||||
|
const InstanceDispatch = vk_constants.InstanceDispatch;
|
||||||
|
|
||||||
|
pub const Allocation = vma.Allocation;
|
||||||
|
pub const Allocator = vma.Allocator;
|
||||||
|
pub const AllocationCreateInfo = vma.AllocationCreateInfo;
|
||||||
|
|
||||||
|
pub const NeonVkBuffer = struct {
|
||||||
|
buffer: vk.Buffer,
|
||||||
|
allocation: vma.Allocation,
|
||||||
|
size: usize,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), vkAllocator: *NeonVkAllocator) void {
|
||||||
|
vkAllocator.destroyBuffer(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NeonVkImage = struct {
|
||||||
|
image: vk.Image,
|
||||||
|
allocation: vma.Allocation,
|
||||||
|
pixelWidth: u32,
|
||||||
|
pixelHeight: u32,
|
||||||
|
|
||||||
|
pub fn deinit(self: *NeonVkImage, allocator: *NeonVkAllocator) void {
|
||||||
|
allocator.destroyImage(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the image ratio of the height over width
|
||||||
|
pub inline fn getImageRatioFloat(self: @This()) f32 {
|
||||||
|
return @as(f32, @floatFromInt(self.pixelHeight)) / @as(f32, @floatFromInt(self.pixelWidth));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AllocationEvent = union(enum) {
|
||||||
|
allocate: struct {
|
||||||
|
alloc: usize,
|
||||||
|
tag: []const u8,
|
||||||
|
},
|
||||||
|
destroy: struct {
|
||||||
|
alloc: usize,
|
||||||
|
tag: []const u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn print(self: @This()) void {
|
||||||
|
switch (self) {
|
||||||
|
.allocate => |allocate| {
|
||||||
|
core.graphics_log("allocate @{d} - {s}", .{ allocate.alloc, allocate.tag });
|
||||||
|
},
|
||||||
|
.destroy => |destroy| {
|
||||||
|
core.graphics_log("destroy @{d} - {s}", .{ destroy.alloc, destroy.tag });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NeonVkAllocator = struct {
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
vmaAllocator: vma.Allocator,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
eventsList: std.ArrayList(AllocationEvent),
|
||||||
|
liveAllocations: std.ArrayList(LiveAlloc),
|
||||||
|
livePipelines: std.AutoHashMap(u64, []u8),
|
||||||
|
vkb: vk_constants.BaseDispatch,
|
||||||
|
vki: vk_constants.InstanceDispatch,
|
||||||
|
vkd: vk_constants.DeviceDispatch,
|
||||||
|
|
||||||
|
const AllocatedObject = union {
|
||||||
|
image: NeonVkImage,
|
||||||
|
buffer: NeonVkBuffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const LiveAlloc = struct {
|
||||||
|
allocation: usize,
|
||||||
|
tag: []const u8,
|
||||||
|
object: AllocatedObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn createStagingBuffer(
|
||||||
|
self: *@This(),
|
||||||
|
bufferSize: u32,
|
||||||
|
comptime tag: []const u8,
|
||||||
|
) !NeonVkBuffer {
|
||||||
|
const bci = vk.BufferCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.size = bufferSize,
|
||||||
|
.usage = .{ .transfer_src_bit = true },
|
||||||
|
.sharing_mode = .exclusive,
|
||||||
|
.queue_family_index_count = 0,
|
||||||
|
.p_queue_family_indices = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vmaCreateInfo = vma.AllocationCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.usage = .cpuOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.createBuffer(bci, vmaCreateInfo, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createPipelineLayout(self: *@This(), dev: vk.Device, plci: vk.PipelineLayoutCreateInfo, tag: []const u8) !vk.PipelineLayout {
|
||||||
|
const pipelineLayout = try self.vkd.createPipelineLayout(dev, &plci, null);
|
||||||
|
|
||||||
|
try self.livePipelines.put(@intFromEnum(pipelineLayout), try core.dupeString(self.allocator, tag));
|
||||||
|
return pipelineLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroyPipelineLayout(self: *@This(), dev: vk.Device, layout: vk.PipelineLayout) void {
|
||||||
|
self.allocator.free(self.livePipelines.get(@intFromEnum(layout)).?);
|
||||||
|
_ = self.livePipelines.remove(@intFromEnum(layout));
|
||||||
|
self.vkd.destroyPipelineLayout(dev, layout, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createGpuBuffer(
|
||||||
|
self: *@This(),
|
||||||
|
bufferSize: u32,
|
||||||
|
options: struct {
|
||||||
|
index_buffer_bit: bool = false,
|
||||||
|
vertex_buffer_bit: bool = false,
|
||||||
|
uniform_texel_buffer_bit: bool = false,
|
||||||
|
storage_texel_buffer_bit: bool = false,
|
||||||
|
uniform_buffer_bit: bool = false,
|
||||||
|
storage_buffer_bit: bool = false,
|
||||||
|
indirect_buffer_bit: bool = false,
|
||||||
|
},
|
||||||
|
comptime tag: []const u8,
|
||||||
|
) !NeonVkBuffer {
|
||||||
|
const bci = vk.BufferCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.size = bufferSize,
|
||||||
|
.usage = .{
|
||||||
|
.transfer_dst_bit = true,
|
||||||
|
.index_buffer_bit = options.index_buffer_bit,
|
||||||
|
.vertex_buffer_bit = options.vertex_buffer_bit,
|
||||||
|
.uniform_buffer_bit = options.uniform_buffer_bit,
|
||||||
|
.storage_buffer_bit = options.storage_buffer_bit,
|
||||||
|
.indirect_buffer_bit = options.indirect_buffer_bit,
|
||||||
|
},
|
||||||
|
.sharing_mode = .exclusive,
|
||||||
|
.queue_family_index_count = 0,
|
||||||
|
.p_queue_family_indices = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vmaCreateInfo = vma.AllocationCreateInfo{
|
||||||
|
.flags = .{},
|
||||||
|
.usage = .gpuOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.createBuffer(bci, vmaCreateInfo, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
vmaAllocatorCreateInfo: vma.AllocatorCreateInfo,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
vkb: vk_constants.BaseDispatch,
|
||||||
|
vki: vk_constants.InstanceDispatch,
|
||||||
|
vkd: vk_constants.DeviceDispatch,
|
||||||
|
) !*@This() {
|
||||||
|
const newAllocator = try allocator.create(@This());
|
||||||
|
|
||||||
|
newAllocator.* = @This(){
|
||||||
|
.vmaAllocator = try vma.Allocator.create(vmaAllocatorCreateInfo),
|
||||||
|
.allocator = allocator,
|
||||||
|
.eventsList = std.ArrayList(AllocationEvent).init(allocator),
|
||||||
|
.liveAllocations = std.ArrayList(LiveAlloc).init(allocator),
|
||||||
|
.livePipelines = std.AutoHashMap(u64, []u8).init(allocator),
|
||||||
|
.vkb = vkb,
|
||||||
|
.vki = vki,
|
||||||
|
.vkd = vkd,
|
||||||
|
};
|
||||||
|
|
||||||
|
return newAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushAllocation(
|
||||||
|
self: *@This(),
|
||||||
|
allocation: vma.Allocation,
|
||||||
|
tag: []const u8,
|
||||||
|
object: AllocatedObject,
|
||||||
|
) !void {
|
||||||
|
try self.liveAllocations.append(.{
|
||||||
|
.allocation = @intFromEnum(allocation),
|
||||||
|
.tag = tag,
|
||||||
|
.object = object,
|
||||||
|
});
|
||||||
|
|
||||||
|
try self.eventsList.append(.{ .allocate = .{
|
||||||
|
.alloc = @intFromEnum(allocation),
|
||||||
|
.tag = tag,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushDestroy(self: *@This(), allocation: vma.Allocation) void {
|
||||||
|
// find the corresponding live allocation
|
||||||
|
var live: LiveAlloc = undefined;
|
||||||
|
var i: u32 = 0;
|
||||||
|
var found: bool = false;
|
||||||
|
|
||||||
|
while (i < self.liveAllocations.items.len) : (i += 1) {
|
||||||
|
if (self.liveAllocations.items[i].allocation == @intFromEnum(allocation)) {
|
||||||
|
found = true;
|
||||||
|
live = self.liveAllocations.items[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
_ = self.liveAllocations.swapRemove(i);
|
||||||
|
} else {
|
||||||
|
core.engine_log("We have a big issue here, a destroy was issued for allocation {any}\n But it is not alive", .{allocation});
|
||||||
|
self.printOutStandingAllocations();
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.eventsList.append(.{ .destroy = .{
|
||||||
|
.alloc = @intFromEnum(allocation),
|
||||||
|
.tag = live.tag,
|
||||||
|
} }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createIndirectCommandBuffer(
|
||||||
|
self: *@This(),
|
||||||
|
bufferSize: u32,
|
||||||
|
comptime tag: []const u8,
|
||||||
|
) !NeonVkBuffer {
|
||||||
|
return try self.createGpuBuffer(bufferSize, .{ .indirect_buffer_bit = true }, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createSsboBuffer(self: *@This(), bufferSize: u32, comptime tag: []const u8) !NeonVkBuffer {
|
||||||
|
const bci = vk.BufferCreateInfo{
|
||||||
|
.size = bufferSize,
|
||||||
|
.usage = .{ .storage_buffer_bit = true },
|
||||||
|
.flags = .{},
|
||||||
|
.sharing_mode = .exclusive,
|
||||||
|
.queue_family_index_count = 0,
|
||||||
|
.p_queue_family_indices = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const aci = vma.AllocationCreateInfo{
|
||||||
|
.usage = .cpuToGpu,
|
||||||
|
};
|
||||||
|
|
||||||
|
return try self.createBuffer(bci, aci, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createBuffer(
|
||||||
|
self: *@This(),
|
||||||
|
bci: vk.BufferCreateInfo,
|
||||||
|
aci: AllocationCreateInfo,
|
||||||
|
comptime tag: []const u8,
|
||||||
|
) !NeonVkBuffer {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
const results = try self.vmaAllocator.createBuffer(bci, aci);
|
||||||
|
|
||||||
|
const object: AllocatedObject = .{
|
||||||
|
.buffer = NeonVkBuffer{
|
||||||
|
.buffer = results.buffer,
|
||||||
|
.allocation = results.allocation,
|
||||||
|
.size = bci.size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
memory.MTAddUntrackedAllocation(bci.size);
|
||||||
|
try self.pushAllocation(results.allocation, tag, object);
|
||||||
|
|
||||||
|
return object.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroyBuffer(self: *@This(), buffer: *NeonVkBuffer) void {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
memory.MTRemoveAllocation(buffer.size);
|
||||||
|
|
||||||
|
self.pushDestroy(buffer.allocation);
|
||||||
|
self.vmaAllocator.destroyBuffer(buffer.buffer, buffer.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroyImage(self: *@This(), image: *NeonVkImage) void {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
self.pushDestroy(image.allocation);
|
||||||
|
self.vmaAllocator.destroyImage(image.image, image.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createImage(
|
||||||
|
self: *@This(),
|
||||||
|
ici: vk.ImageCreateInfo,
|
||||||
|
aci: AllocationCreateInfo,
|
||||||
|
comptime tag: []const u8,
|
||||||
|
) !NeonVkImage {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
const result = try self.vmaAllocator.createImage(ici, aci);
|
||||||
|
|
||||||
|
const object: AllocatedObject = .{ .image = .{
|
||||||
|
.image = result.image,
|
||||||
|
.allocation = result.allocation,
|
||||||
|
.pixelWidth = ici.extent.width,
|
||||||
|
.pixelHeight = ici.extent.height,
|
||||||
|
} };
|
||||||
|
|
||||||
|
try self.pushAllocation(result.allocation, tag, object);
|
||||||
|
|
||||||
|
return object.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapBuffer(self: *@This(), comptime T: type, buffer: NeonVkBuffer) ![]T {
|
||||||
|
var slice: []T = undefined;
|
||||||
|
slice.ptr = try self.mapMemory(buffer, T);
|
||||||
|
slice.len = buffer.size / @sizeOf(T);
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapMemorySlice(self: *@This(), comptime T: type, buffer: NeonVkBuffer, size: usize) ![]T {
|
||||||
|
var slice: []T = undefined;
|
||||||
|
slice.ptr = try self.mapMemory(buffer, T);
|
||||||
|
slice.len = size;
|
||||||
|
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapMemory(self: *@This(), buffer: NeonVkBuffer, comptime T: type) ![*]T {
|
||||||
|
return try self.vmaAllocator.mapMemory(buffer.allocation, T);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmapMemory(self: *@This(), buffer: NeonVkBuffer) void {
|
||||||
|
self.vmaAllocator.unmapMemory(buffer.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printEventsLog(self: @This()) void {
|
||||||
|
for (self.eventsList.items) |item| {
|
||||||
|
item.print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn areAllocationsOutstanding(self: *@This()) bool {
|
||||||
|
return self.liveAllocations.items.len > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printOutStandingAllocations(self: *@This()) void {
|
||||||
|
core.graphics_log(" == There are {d} allocations outstanding", .{self.liveAllocations.items.len});
|
||||||
|
|
||||||
|
for (self.liveAllocations.items) |alloc| {
|
||||||
|
core.graphics_log("live allocation@{d} tag:\'{s}\' {any}", .{ alloc.allocation, alloc.tag, alloc.object });
|
||||||
|
}
|
||||||
|
|
||||||
|
core.graphics_logs("--- Event log below --- ");
|
||||||
|
|
||||||
|
self.printEventsLog();
|
||||||
|
core.graphics_logs("end of report.");
|
||||||
|
core.forceFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This()) void {
|
||||||
|
self.eventsList.deinit();
|
||||||
|
self.liveAllocations.deinit();
|
||||||
|
{
|
||||||
|
var iter = self.livePipelines.iterator();
|
||||||
|
while (iter.next()) |i| {
|
||||||
|
self.allocator.free(i.value_ptr.*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.livePipelines.deinit();
|
||||||
|
self.vmaAllocator.destroy();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// global api
|
||||||
|
//
|
||||||
|
// i hate lugging these variables around.
|
||||||
|
// device and cmd buffers are fine,
|
||||||
|
// but the dispatch variables are going to be kept here and easily accessible.
|
||||||
|
|
||||||
|
pub var _vkb: constants.BaseDispatch = undefined;
|
||||||
|
pub var _vki: constants.InstanceDispatch = undefined;
|
||||||
|
pub var _vkd: constants.DeviceDispatch = undefined;
|
||||||
|
|
||||||
|
pub const vkb = &_vkb;
|
||||||
|
pub const vki = &_vki;
|
||||||
|
pub const vkd = &_vkd;
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const constants = @import("vk_constants.zig");
|
||||||
|
|
@ -0,0 +1,373 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const graphics = @import("graphics.zig");
|
||||||
|
|
||||||
|
const core = @import("core");
|
||||||
|
const assets = @import("assets");
|
||||||
|
const vk_utils = @import("vk_utils.zig");
|
||||||
|
const vkinit = @import("vk_init.zig");
|
||||||
|
const vk_cubemap = @import("vk_renderer/vk_cubemap.zig");
|
||||||
|
|
||||||
|
const tracy = core.tracy;
|
||||||
|
const materials = @import("materials.zig");
|
||||||
|
const vk_renderer = @import("vk_renderer.zig");
|
||||||
|
const mesh = @import("mesh.zig");
|
||||||
|
const texture = @import("texture.zig");
|
||||||
|
|
||||||
|
const NeonVkContext = vk_renderer.NeonVkContext;
|
||||||
|
const Material = materials.Material;
|
||||||
|
const Mesh = mesh.Mesh;
|
||||||
|
const Texture = texture.Texture;
|
||||||
|
|
||||||
|
pub const TextureLoader = struct {
|
||||||
|
pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Texture", @This());
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
|
||||||
|
const StagedTextureDescription = struct {
|
||||||
|
name: core.Name,
|
||||||
|
stagingResults: vk_utils.LoadAndStageImage,
|
||||||
|
textureListResults: ?[]vk_utils.LoadAndStageImage = null,
|
||||||
|
assetRef: assets.AssetRef,
|
||||||
|
properties: assets.AssetPropertiesBag,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), gc: *NeonVkContext) void {
|
||||||
|
self.stagingResults.deinit(gc.vkAllocator);
|
||||||
|
if (self.textureListResults) |results| {
|
||||||
|
for (results) |*result| {
|
||||||
|
result.deinit(gc.vkAllocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RTAssetsReady = struct {
|
||||||
|
name: core.Name,
|
||||||
|
texture: *Texture,
|
||||||
|
textureSet: vk.DescriptorSet,
|
||||||
|
textureId: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
gc: *NeonVkContext,
|
||||||
|
assetsReady: core.RingQueue(StagedTextureDescription),
|
||||||
|
rtAssetsReady: core.RingQueue(RTAssetsReady),
|
||||||
|
discarding: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
||||||
|
|
||||||
|
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, props: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
|
||||||
|
if (self.discarding.load(.seq_cst)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var z = tracy.ZoneN(@src(), "TextureLoader loadAsset");
|
||||||
|
const Lambda = struct {
|
||||||
|
loader: *TextureLoader,
|
||||||
|
assetRef: assets.AssetRef,
|
||||||
|
gc: *NeonVkContext,
|
||||||
|
properties: assets.AssetPropertiesBag,
|
||||||
|
|
||||||
|
pub fn eFunc(ctx: @This()) !void {
|
||||||
|
var z1 = tracy.ZoneN(@src(), "Loading file from TextureLoader");
|
||||||
|
const gc = ctx.gc;
|
||||||
|
defer {
|
||||||
|
_ = ctx.gc.outstandingJobsCount.fetchSub(1, .seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadAndStageResults: vk_utils.LoadAndStageImage = undefined;
|
||||||
|
if (!ctx.properties.textureCube) {
|
||||||
|
loadAndStageResults = try vk_utils.load_and_stage_image_from_file(gc, ctx.properties.path);
|
||||||
|
errdefer loadAndStageResults.deinit(gc.vkAllocator);
|
||||||
|
} else {
|
||||||
|
loadAndStageResults = try vk_cubemap.stageCubeTexture(ctx.properties.textureList.?);
|
||||||
|
errdefer loadAndStageResults.deinit(gc.vkAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetRefName = ctx.assetRef.name;
|
||||||
|
|
||||||
|
tracy.Message(assetRefName.utf8());
|
||||||
|
tracy.Message(ctx.properties.path);
|
||||||
|
|
||||||
|
core.engine_log("loaded: {s} from: {s}", .{ assetRefName.utf8(), ctx.properties.path });
|
||||||
|
var loadedDescription = StagedTextureDescription{
|
||||||
|
.name = ctx.assetRef.name,
|
||||||
|
.stagingResults = loadAndStageResults,
|
||||||
|
.assetRef = ctx.assetRef,
|
||||||
|
.properties = ctx.properties,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ctx.properties.textureList) |textureList| {
|
||||||
|
const tlResults = try ctx.gc.allocator.alloc(vk_utils.LoadAndStageImage, textureList.len);
|
||||||
|
errdefer ctx.gc.allocator.free(tlResults);
|
||||||
|
|
||||||
|
for (textureList, 0..) |tPath, i| {
|
||||||
|
const rv = vk_utils.load_and_stage_image_from_file(gc, tPath) catch {
|
||||||
|
core.engine_log("unable to load file {s}", .{tPath});
|
||||||
|
return error.FailedToLoad;
|
||||||
|
};
|
||||||
|
errdefer rv.deinit();
|
||||||
|
tlResults[i] = rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedDescription.textureListResults = tlResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
z1.End();
|
||||||
|
|
||||||
|
ctx.loader.assetsReady.pushLocked(loadedDescription) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func(ctx: @This(), _: *core.JobContext) void {
|
||||||
|
ctx.eFunc() catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = self.gc.outstandingJobsCount.fetchAdd(1, .seq_cst);
|
||||||
|
core.dispatchJob(Lambda{
|
||||||
|
.loader = self,
|
||||||
|
.gc = self.gc,
|
||||||
|
.assetRef = assetRef,
|
||||||
|
.properties = props.?,
|
||||||
|
}) catch return error.UnableToLoad;
|
||||||
|
|
||||||
|
z.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn processRenderThreadEvents(ptr: *anyopaque) void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(ptr));
|
||||||
|
self.processEventInner() catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createImageFromStagingResult(self: *@This(), name: core.Name, stagingResults: *vk_utils.LoadAndStageImage, properties: assets.AssetPropertiesBag) core.EngineDataEventError!void {
|
||||||
|
const gc = self.gc;
|
||||||
|
var stagingBuffer = stagingResults.stagingBuffer;
|
||||||
|
const image = stagingResults.image;
|
||||||
|
|
||||||
|
if (stagingResults.cubeOffsets != null) {
|
||||||
|
vk_cubemap.submitTextureCube(&gc.uploader, stagingResults) catch return error.UnknownStatePanic;
|
||||||
|
stagingBuffer.deinit(gc.vkAllocator);
|
||||||
|
|
||||||
|
var ivc = vkinit.imageViewCreateInfo(
|
||||||
|
.r8g8b8a8_srgb,
|
||||||
|
image.image,
|
||||||
|
.{ .color_bit = true },
|
||||||
|
stagingResults.mipLevel,
|
||||||
|
);
|
||||||
|
ivc.view_type = .cube;
|
||||||
|
ivc.subresource_range.layer_count = 6;
|
||||||
|
const imageView = gc.vkd.createImageView(gc.dev, &ivc, null) catch return error.UnknownStatePanic;
|
||||||
|
const newTexture = gc.allocator.create(Texture) catch return error.UnknownStatePanic;
|
||||||
|
newTexture.* = Texture{
|
||||||
|
.image = image,
|
||||||
|
.imageView = imageView,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rv = vk_utils.createDescriptorSetForImage(
|
||||||
|
gc.dev,
|
||||||
|
gc.descriptorPool,
|
||||||
|
gc.singleTextureSetLayout,
|
||||||
|
imageView,
|
||||||
|
gc.cubeSampler,
|
||||||
|
false,
|
||||||
|
) catch return error.UnknownStatePanic;
|
||||||
|
|
||||||
|
self.rtAssetsReady.pushLocked(.{
|
||||||
|
.name = name,
|
||||||
|
.texture = newTexture,
|
||||||
|
.textureSet = rv.textureSet,
|
||||||
|
.textureId = rv.textureId,
|
||||||
|
}) catch return error.UnknownStatePanic;
|
||||||
|
} else {
|
||||||
|
vk_utils.submit_copy_from_staging(gc, stagingBuffer, image, stagingResults.mipLevel) catch return error.UnknownStatePanic;
|
||||||
|
stagingBuffer.deinit(gc.vkAllocator);
|
||||||
|
|
||||||
|
var imageViewCreate = vkinit.imageViewCreateInfo(
|
||||||
|
.r8g8b8a8_srgb,
|
||||||
|
image.image,
|
||||||
|
.{ .color_bit = true },
|
||||||
|
stagingResults.mipLevel,
|
||||||
|
);
|
||||||
|
const imageView = gc.vkd.createImageView(gc.dev, &imageViewCreate, null) catch return error.UnknownStatePanic;
|
||||||
|
const newTexture = gc.allocator.create(Texture) catch return error.UnknownStatePanic;
|
||||||
|
|
||||||
|
newTexture.* = Texture{
|
||||||
|
.image = image,
|
||||||
|
.imageView = imageView,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sampler = if (properties.textureUseBlockySampler) gc.blockySampler else gc.linearSampler;
|
||||||
|
const rv = vk_utils.createDescriptorSetForImage(
|
||||||
|
gc.dev,
|
||||||
|
gc.descriptorPool,
|
||||||
|
gc.singleTextureSetLayout,
|
||||||
|
imageView,
|
||||||
|
sampler,
|
||||||
|
true,
|
||||||
|
) catch return error.UnknownStatePanic;
|
||||||
|
|
||||||
|
self.rtAssetsReady.pushLocked(.{
|
||||||
|
.name = name,
|
||||||
|
.texture = newTexture,
|
||||||
|
.textureSet = rv.textureSet,
|
||||||
|
.textureId = rv.textureId,
|
||||||
|
}) catch return error.UnknownStatePanic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn processEventInner(self: *@This()) core.EngineDataEventError!void {
|
||||||
|
if (self.assetsReady.count() > 0) {
|
||||||
|
self.assetsReady.lock();
|
||||||
|
defer self.assetsReady.unlock();
|
||||||
|
while (self.assetsReady.popFromUnlocked()) |ar| {
|
||||||
|
var assetReady = ar;
|
||||||
|
var z1 = tracy.ZoneN(@src(), "Uploading asset loaded by TextureLoader");
|
||||||
|
tracy.Message("TextureLoader");
|
||||||
|
tracy.Message(assetReady.assetRef.name.utf8());
|
||||||
|
tracy.Message(assetReady.properties.path);
|
||||||
|
core.engine_log("async texture load complete registry: {s}", .{assetReady.name.utf8()});
|
||||||
|
try self.createImageFromStagingResult(assetReady.name, &assetReady.stagingResults, assetReady.properties);
|
||||||
|
|
||||||
|
if (assetReady.textureListResults) |results| {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
for (results, 0..) |res, i| {
|
||||||
|
var r = res;
|
||||||
|
var arName = assetReady.name;
|
||||||
|
const newName = std.fmt.bufPrint(&buf, "{s}[{d}]", .{ arName.utf8(), i }) catch return core.EngineDataEventError.OutOfMemory;
|
||||||
|
|
||||||
|
try self.createImageFromStagingResult(core.MakeName(newName), &r, assetReady.properties);
|
||||||
|
}
|
||||||
|
self.gc.allocator.free(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
z1.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processing events, some should really be processing events rather than
|
||||||
|
pub fn processEvents(self: *@This(), frameNumber: u64) core.EngineDataEventError!void {
|
||||||
|
_ = frameNumber;
|
||||||
|
if (self.rtAssetsReady.count() > 0) {
|
||||||
|
self.rtAssetsReady.lock();
|
||||||
|
defer self.rtAssetsReady.unlock();
|
||||||
|
while (self.rtAssetsReady.popFromUnlocked()) |a| {
|
||||||
|
self.gc.install_texture_into_registry(a.name, a.texture, a.textureSet, a.textureId) catch return error.UnknownStatePanic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discardAll(self: *@This()) void {
|
||||||
|
self.discarding.store(true, .seq_cst);
|
||||||
|
core.graphics_log("discarding {d} outstanding jobs", .{self.assetsReady.count()});
|
||||||
|
|
||||||
|
self.assetsReady.lock();
|
||||||
|
defer self.assetsReady.unlock();
|
||||||
|
|
||||||
|
while (self.assetsReady.popFromUnlocked()) |assetReady| {
|
||||||
|
var copy = assetReady;
|
||||||
|
StagedTextureDescription.deinit(©, self.gc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.gc = vk_renderer.gContext,
|
||||||
|
//todo: the EngineObjectVTable init function should have a handleable error
|
||||||
|
.assetsReady = core.RingQueue(StagedTextureDescription).init(allocator, 1024) catch unreachable,
|
||||||
|
.rtAssetsReady = core.RingQueue(RTAssetsReady).init(allocator, 1024) catch unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.gc.renderthread.installListener(self, processRenderThreadEvents);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.assetsReady.deinit();
|
||||||
|
self.rtAssetsReady.deinit();
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MeshLoader = struct {
|
||||||
|
pub var LoaderInterfaceVTable = assets.AssetLoaderInterface.from("Mesh", @This());
|
||||||
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
|
||||||
|
gc: *NeonVkContext,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*@This() {
|
||||||
|
const self = try allocator.create(@This());
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.gc = vk_renderer.gContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
|
||||||
|
_ = self;
|
||||||
|
const sourceType = getSourceType(propertiesBag);
|
||||||
|
core.engine_log("loading mesh asset {s} [{s}]", .{ propertiesBag.?.path, if (sourceType) |s| @tagName(s) else "default" });
|
||||||
|
graphics.loadIndexedMeshForPooling(assetRef.name, .{
|
||||||
|
.path = propertiesBag.?.path,
|
||||||
|
.sourceType = getSourceType(propertiesBag),
|
||||||
|
.skeletonName = if (propertiesBag.?.skeletonName) |skName| core.MakeName(skName) else null,
|
||||||
|
}) catch return error.UnableToLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getSourceType(propertiesBag: ?assets.AssetPropertiesBag) ?graphics.MeshSourceType {
|
||||||
|
if (propertiesBag) |bag| {
|
||||||
|
if (bag.meshType) |meshType| {
|
||||||
|
if (std.mem.eql(u8, meshType, "obj")) {
|
||||||
|
return graphics.MeshSourceType.obj;
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, meshType, "gltf")) {
|
||||||
|
return graphics.MeshSourceType.gltf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to deduce it by file name, if nothing is set.
|
||||||
|
const ext = core.getFileExtension(bag.path);
|
||||||
|
if (std.mem.eql(u8, ext, ".obj")) {
|
||||||
|
return graphics.MeshSourceType.obj;
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, ext, ".gltf")) {
|
||||||
|
return graphics.MeshSourceType.gltf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, ext, ".glb")) {
|
||||||
|
return graphics.MeshSourceType.gltf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discardAll(self: *@This()) void {
|
||||||
|
// totally synchronous, nothing to do for a discard
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var gTextureLoader: *TextureLoader = undefined;
|
||||||
|
pub var gMeshLoader: *MeshLoader = undefined;
|
||||||
|
|
||||||
|
pub fn init_loaders(allocator: std.mem.Allocator) !void {
|
||||||
|
gTextureLoader = try core.createObject(TextureLoader, .{
|
||||||
|
.responds_to_events = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
gMeshLoader = try allocator.create(MeshLoader);
|
||||||
|
gMeshLoader.* = .{ .gc = vk_renderer.gContext };
|
||||||
|
|
||||||
|
try assets.gAssetSys.registerLoader(gTextureLoader);
|
||||||
|
try assets.gAssetSys.registerLoader(gMeshLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// submit an abort message to TextureLoader and MeshLoader
|
||||||
|
pub fn discardAll() void {
|
||||||
|
gTextureLoader.discardAll();
|
||||||
|
gMeshLoader.discardAll();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const core = @import("core");
|
||||||
|
|
||||||
|
pub const NumFrames = NUM_FRAMES;
|
||||||
|
pub const FrameTimeout = 10_000_000_000; // 10 full second frame timeout
|
||||||
|
pub const MAX_OBJECTS = 100_000; // 100k objects ought to be enough for anyone
|
||||||
|
pub const NUM_FRAMES: usize = 2;
|
||||||
|
pub const DEVICE_LAYERS = [_]core.CStr{VK_KHRONOS_VALIDATION_LAYER_STRING};
|
||||||
|
|
||||||
|
pub const MAX_SKIN_SLOTS = 100_000;
|
||||||
|
|
||||||
|
pub const required_device_layers = [_]core.CStr{"VK_LAYER_KHRONOS_validation"};
|
||||||
|
|
||||||
|
pub const VK_KHRONOS_VALIDATION_LAYER_STRING: core.CStr = "VK_LAYER_KHRONOS_validation";
|
||||||
|
|
||||||
|
pub const BaseDispatch = vk.BaseWrapper(.{
|
||||||
|
.createInstance = true,
|
||||||
|
.getInstanceProcAddr = true,
|
||||||
|
.enumerateInstanceVersion = true,
|
||||||
|
.enumerateInstanceLayerProperties = true,
|
||||||
|
.enumerateInstanceExtensionProperties = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const InstanceDispatch = vk.InstanceWrapper(.{
|
||||||
|
.getPhysicalDeviceFeatures = true,
|
||||||
|
.destroyInstance = true,
|
||||||
|
.createDevice = true,
|
||||||
|
.destroySurfaceKHR = true,
|
||||||
|
.enumeratePhysicalDevices = true,
|
||||||
|
.getPhysicalDeviceProperties = true,
|
||||||
|
.enumerateDeviceExtensionProperties = true,
|
||||||
|
.getPhysicalDeviceSurfaceFormatsKHR = true,
|
||||||
|
.getPhysicalDeviceSurfacePresentModesKHR = true,
|
||||||
|
.getPhysicalDeviceSurfaceCapabilitiesKHR = true,
|
||||||
|
.getPhysicalDeviceQueueFamilyProperties = true,
|
||||||
|
.getPhysicalDeviceSurfaceSupportKHR = true,
|
||||||
|
.getPhysicalDeviceMemoryProperties = true,
|
||||||
|
.getPhysicalDeviceFormatProperties = true,
|
||||||
|
.getDeviceProcAddr = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const DeviceDispatch = vk.DeviceWrapper(.{
|
||||||
|
.resetCommandBuffer = true,
|
||||||
|
.destroyDevice = true,
|
||||||
|
.getDeviceQueue = true,
|
||||||
|
.createSemaphore = true,
|
||||||
|
.createFence = true,
|
||||||
|
.createImageView = true,
|
||||||
|
.createImage = true,
|
||||||
|
.destroyImage = true,
|
||||||
|
.destroyImageView = true,
|
||||||
|
.destroySemaphore = true,
|
||||||
|
.destroyFence = true,
|
||||||
|
.getSwapchainImagesKHR = true,
|
||||||
|
.createSwapchainKHR = true,
|
||||||
|
.destroySwapchainKHR = true,
|
||||||
|
.acquireNextImageKHR = true,
|
||||||
|
.deviceWaitIdle = true,
|
||||||
|
.waitForFences = true,
|
||||||
|
.resetFences = true,
|
||||||
|
.queueSubmit = true,
|
||||||
|
.queuePresentKHR = true,
|
||||||
|
.createCommandPool = true,
|
||||||
|
.destroyCommandPool = true,
|
||||||
|
.allocateCommandBuffers = true,
|
||||||
|
.cmdBlitImage = true,
|
||||||
|
.freeCommandBuffers = true,
|
||||||
|
.queueWaitIdle = true,
|
||||||
|
.createShaderModule = true,
|
||||||
|
.destroyShaderModule = true,
|
||||||
|
.createPipelineLayout = true,
|
||||||
|
.destroyPipelineLayout = true,
|
||||||
|
.createDescriptorSetLayout = true,
|
||||||
|
.destroyDescriptorSetLayout = true,
|
||||||
|
.createDescriptorPool = true,
|
||||||
|
.allocateDescriptorSets = true,
|
||||||
|
.freeDescriptorSets = true,
|
||||||
|
.updateDescriptorSets = true,
|
||||||
|
.destroyDescriptorPool = true,
|
||||||
|
.createRenderPass = true,
|
||||||
|
.destroyRenderPass = true,
|
||||||
|
.createGraphicsPipelines = true,
|
||||||
|
.destroyPipeline = true,
|
||||||
|
.createFramebuffer = true,
|
||||||
|
.destroyFramebuffer = true,
|
||||||
|
.beginCommandBuffer = true,
|
||||||
|
.endCommandBuffer = true,
|
||||||
|
.allocateMemory = true,
|
||||||
|
.freeMemory = true,
|
||||||
|
.createBuffer = true,
|
||||||
|
.destroyBuffer = true,
|
||||||
|
.getBufferMemoryRequirements = true,
|
||||||
|
.mapMemory = true,
|
||||||
|
.unmapMemory = true,
|
||||||
|
.bindBufferMemory = true,
|
||||||
|
.cmdBeginRenderPass = true,
|
||||||
|
.cmdEndRenderPass = true,
|
||||||
|
.cmdBindPipeline = true,
|
||||||
|
.cmdBindIndexBuffer = true,
|
||||||
|
.cmdDrawIndexed = true,
|
||||||
|
.cmdDraw = true,
|
||||||
|
.cmdSetViewport = true,
|
||||||
|
.cmdSetScissor = true,
|
||||||
|
.cmdBindVertexBuffers = true,
|
||||||
|
.cmdCopyBuffer = true,
|
||||||
|
.cmdPushConstants = true,
|
||||||
|
.cmdPipelineBarrier = true,
|
||||||
|
.cmdBindDescriptorSets = true,
|
||||||
|
.cmdCopyBufferToImage = true,
|
||||||
|
.createSampler = true,
|
||||||
|
.destroySampler = true,
|
||||||
|
.cmdDrawIndexedIndirect = true,
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue