summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/asset_packer.cc63
-rw-r--r--tools/timeline_editor/index.html6
-rw-r--r--tools/validate_uniforms.py178
3 files changed, 243 insertions, 4 deletions
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc
index 0d26cf6..4aaa0e7 100644
--- a/tools/asset_packer.cc
+++ b/tools/asset_packer.cc
@@ -52,6 +52,7 @@ struct AssetBuildInfo {
std::string name;
std::string filename; // Original filename for static assets
bool is_procedural;
+ bool is_gpu_procedural;
std::string proc_func_name; // Function name string
std::vector<float> proc_params; // Parameters for procedural function
@@ -182,9 +183,64 @@ int main(int argc, char* argv[]) {
info.params_array_name = "ASSET_PROC_PARAMS_" + info.name;
info.func_name_str_name = "ASSET_PROC_FUNC_STR_" + info.name;
info.is_procedural = false;
+ info.is_gpu_procedural = false;
- if (compression_type_str.rfind("PROC(", 0) == 0) {
+ if (compression_type_str.rfind("PROC_GPU(", 0) == 0) {
info.is_procedural = true;
+ info.is_gpu_procedural = true;
+ size_t open_paren = compression_type_str.find('(');
+ size_t close_paren = compression_type_str.rfind(')');
+ if (open_paren == std::string::npos ||
+ close_paren == std::string::npos) {
+ fprintf(stderr,
+ "Error: Invalid PROC_GPU() syntax for asset: %s, string: [%s]\n",
+ info.name.c_str(), compression_type_str.c_str());
+ return 1;
+ }
+ std::string func_and_params_str = compression_type_str.substr(
+ open_paren + 1, close_paren - open_paren - 1);
+
+ size_t params_start = func_and_params_str.find(',');
+ if (params_start != std::string::npos) {
+ std::string params_str = func_and_params_str.substr(params_start + 1);
+ info.proc_func_name = func_and_params_str.substr(0, params_start);
+
+ size_t current_pos = 0;
+ while (current_pos < params_str.length()) {
+ size_t comma_pos = params_str.find(',', current_pos);
+ std::string param_val_str =
+ (comma_pos == std::string::npos)
+ ? params_str.substr(current_pos)
+ : params_str.substr(current_pos, comma_pos - current_pos);
+ param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n"));
+ param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1);
+ try {
+ info.proc_params.push_back(std::stof(param_val_str));
+ } catch (...) {
+ fprintf(stderr, "Error: Invalid proc param for %s: %s\n",
+ info.name.c_str(), param_val_str.c_str());
+ return 1;
+ }
+ if (comma_pos == std::string::npos)
+ break;
+ current_pos = comma_pos + 1;
+ }
+ } else {
+ info.proc_func_name = func_and_params_str;
+ }
+
+ // Validate GPU procedural function name
+ if (info.proc_func_name != "gen_noise" &&
+ info.proc_func_name != "gen_perlin" &&
+ info.proc_func_name != "gen_grid") {
+ fprintf(stderr,
+ "Error: PROC_GPU only supports gen_noise, gen_perlin, gen_grid, got: %s for asset: %s\n",
+ info.proc_func_name.c_str(), info.name.c_str());
+ return 1;
+ }
+ } else if (compression_type_str.rfind("PROC(", 0) == 0) {
+ info.is_procedural = true;
+ info.is_gpu_procedural = false;
size_t open_paren = compression_type_str.find('(');
size_t close_paren = compression_type_str.rfind(')');
if (open_paren == std::string::npos ||
@@ -500,12 +556,13 @@ int main(int argc, char* argv[]) {
for (const auto& info : asset_build_infos) {
fprintf(assets_data_cc_file, " { ");
if (info.is_procedural) {
- fprintf(assets_data_cc_file, "nullptr, 0, true, %s, %s, %zu",
+ fprintf(assets_data_cc_file, "nullptr, 0, true, %s, %s, %s, %zu",
+ info.is_gpu_procedural ? "true" : "false",
info.func_name_str_name.c_str(), info.params_array_name.c_str(),
info.proc_params.size());
} else {
fprintf(assets_data_cc_file,
- "%s, ASSET_SIZE_%s, false, nullptr, nullptr, 0",
+ "%s, ASSET_SIZE_%s, false, false, nullptr, nullptr, 0",
info.data_array_name.c_str(), info.name.c_str());
}
fprintf(assets_data_cc_file, " },\n");
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index 074b711..db71beb 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -601,7 +601,11 @@
const modifier = effect.priorityModifier || '+';
output += ` EFFECT ${modifier} ${effect.className} ${effect.startTime.toFixed(2)} ${effect.endTime.toFixed(2)}`;
if (effect.args) {
- output += ` ${effect.args}`;
+ // Strip priority comments from args
+ const cleanArgs = effect.args.replace(/\s*#\s*Priority:\s*\d+/i, '').trim();
+ if (cleanArgs) {
+ output += ` ${cleanArgs}`;
+ }
}
output += '\n';
}
diff --git a/tools/validate_uniforms.py b/tools/validate_uniforms.py
new file mode 100644
index 0000000..40d1b0f
--- /dev/null
+++ b/tools/validate_uniforms.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+
+import sys
+import re
+import os
+
+# WGSL alignment rules (simplified for common types)
+WGSL_ALIGNMENT = {
+ "f32": 4,
+ "vec2<f32>": 8,
+ "vec3<f32>": 16,
+ "vec4<f32>": 16,
+ # Add other types as needed (e.g., u32, i32, mat4x4<f32>)
+}
+
+def get_wgsl_type_size_and_alignment(type_name):
+ type_name = type_name.strip()
+ if type_name in WGSL_ALIGNMENT:
+ return WGSL_ALIGNMENT[type_name], WGSL_ALIGNMENT[type_name]
+ # Handle arrays, e.g., array<f32, 5>
+ if type_name.startswith("array"):
+ match = re.search(r"array<([\w<>, ]+)>", type_name)
+ if match:
+ inner_type = match.group(1).split(",")[0].strip()
+ # For simplicity, assume scalar array doesn't change alignment of base type
+ return get_wgsl_type_size_and_alignment(inner_type)
+ # Handle structs recursively (simplified, assumes no nested structs for now)
+ return 0, 0 # Unknown or complex type
+
+def parse_wgsl_struct(wgsl_content):
+ structs = {}
+ # Regex to find struct definitions: struct StructName { ... }
+ struct_matches = re.finditer(r"struct\s+(\w+)\s*\{\s*(.*?)\s*\}", wgsl_content, re.DOTALL)
+ for struct_match in struct_matches:
+ struct_name = struct_match.group(1)
+ members_content = struct_match.group(2)
+ members = []
+ # Regex to find members: member_name: member_type
+ # Adjusted regex to handle types with brackets and spaces, and comments.
+ # CHANGED: \s to [ \t] to avoid consuming newlines
+ member_matches = re.finditer(r"(\w+)\s*:\s*([\w<>,\[\] \t]+)(?:\s*//.*)?", members_content)
+ for member_match in member_matches:
+ member_name = member_match.group(1)
+ member_type = member_match.group(2).strip()
+ if member_type.endswith(','):
+ member_type = member_type[:-1].strip()
+ members.append((member_name, member_type))
+ structs[struct_name] = members
+ # print(f"DEBUG: Parsed WGSL struct '{struct_name}' with members: {members}")
+ return structs
+
+def find_embedded_wgsl_in_cpp(cpp_content):
+ # Regex to find raw string literals R"(...)" which often contain WGSL
+ wgsl_blocks = []
+ matches = re.finditer(r'R"\((.*?)\)"', cpp_content, re.DOTALL)
+ for match in matches:
+ wgsl_blocks.append(match.group(1))
+ return wgsl_blocks
+
+def calculate_wgsl_struct_size(struct_name, struct_members):
+ total_size = 0
+ max_alignment = 0
+ members_info = []
+
+ for member_name, member_type in struct_members:
+ size, alignment = get_wgsl_type_size_and_alignment(member_type)
+ if size == 0: # If type is unknown or complex, we can't reliably calculate
+ # print(f"Warning: Unknown or complex WGSL type '{member_type}' for member '{member_name}'. Cannot reliably calculate size.", file=sys.stderr)
+ return 0, 0
+ members_info.append((member_name, member_type, size, alignment))
+ max_alignment = max(max_alignment, alignment)
+
+ current_offset = 0
+ for member_name, member_type, size, alignment in members_info:
+ # Align current offset to the alignment of the current member
+ current_offset = (current_offset + alignment - 1) & ~(alignment - 1)
+ current_offset += size
+
+ # The total size of the struct is the final offset, padded to the max alignment
+ if max_alignment > 0:
+ total_size = (current_offset + max_alignment - 1) & ~(max_alignment - 1)
+ else:
+ total_size = current_offset
+
+ return total_size, max_alignment
+
+def parse_cpp_static_asserts(cpp_content):
+ cpp_structs = {}
+ # Regex to find C++ struct definitions with static_asserts for sizeof
+ # This regex is simplified and might need adjustments for more complex C++ code
+ struct_matches = re.finditer(r"struct\s+(\w+)\s*\{\s*(.*?)\s*\}\s*;.*?static_assert\(sizeof\(\1\)\s*==\s*(\d+)\s*,.*?\);", cpp_content, re.DOTALL | re.MULTILINE)
+ for struct_match in struct_matches:
+ struct_name = struct_match.group(1)
+ members_content = struct_match.group(2)
+ expected_size = int(struct_match.group(3))
+ members = []
+ # Regex to find members: type member_name;
+ member_matches = re.finditer(r"(.*?)\s+(\w+)\s*(?:=\s*.*?|\s*\{.*?\})?;", members_content)
+ for member_match in member_matches:
+ member_type = member_match.group(1).strip()
+ member_name = member_match.group(2).strip()
+ members.append((member_name, member_type))
+ cpp_structs[struct_name] = {"members": members, "expected_size": expected_size}
+ return cpp_structs
+
+def validate_uniforms(wgsl_files, cpp_files):
+ all_wgsl_structs = {}
+
+ # Parse separate WGSL files
+ for file_path in wgsl_files:
+ try:
+ with open(file_path, 'r') as f:
+ wgsl_content = f.read()
+ structs = parse_wgsl_struct(wgsl_content)
+ all_wgsl_structs.update(structs)
+ except Exception as e:
+ print(f"Error parsing WGSL file {file_path}: {e}", file=sys.stderr)
+ continue
+
+ # Parse C++ files for embedded WGSL and static_asserts
+ for cpp_file_path in cpp_files:
+ try:
+ with open(cpp_file_path, 'r') as f:
+ cpp_content = f.read()
+
+ # Parse embedded WGSL
+ wgsl_blocks = find_embedded_wgsl_in_cpp(cpp_content)
+ for block in wgsl_blocks:
+ structs = parse_wgsl_struct(block)
+ all_wgsl_structs.update(structs)
+
+ # Parse C++ structs and static_asserts
+ cpp_structs = parse_cpp_static_asserts(cpp_content)
+ for struct_name, data in cpp_structs.items():
+ expected_size = data["expected_size"]
+ # Try to find the matching WGSL struct
+ if struct_name in all_wgsl_structs:
+ wgsl_members = all_wgsl_structs[struct_name]
+ calculated_wgsl_size, wgsl_max_alignment = calculate_wgsl_struct_size(struct_name, wgsl_members)
+
+ if calculated_wgsl_size == 0: # If calculation failed
+ # print(f"Validation Warning for '{struct_name}': Could not calculate WGSL size.")
+ continue
+
+ if calculated_wgsl_size != expected_size:
+ print(f"Validation Mismatch for '{struct_name}':\n WGSL Calculated Size: {calculated_wgsl_size}\n C++ Expected Size: {expected_size}\n Max WGSL Alignment: {wgsl_max_alignment}", file=sys.stderr)
+ sys.exit(1)
+ else:
+ print(f"Validation OK for '{struct_name}': Size {calculated_wgsl_size} matches C++ expected size.")
+ else:
+ print(f"Validation Warning for '{struct_name}': Matching WGSL struct not found.")
+ except Exception as e:
+ print(f"Error processing C++ file {cpp_file_path}: {e}", file=sys.stderr)
+ continue
+
+def main():
+ if len(sys.argv) < 3:
+ print("Usage: validate_uniforms.py <wgsl_dir_or_file> <cpp_file1> [<cpp_file2> ...]", file=sys.stderr)
+ sys.exit(1)
+
+ wgsl_input = sys.argv[1]
+ cpp_files = sys.argv[2:]
+
+ wgsl_files = []
+ if os.path.isfile(wgsl_input):
+ wgsl_files.append(wgsl_input)
+ elif os.path.isdir(wgsl_input):
+ for root, _, files in os.walk(wgsl_input):
+ for file in files:
+ if file.endswith(".wgsl"):
+ wgsl_files.append(os.path.join(root, file))
+
+ # We proceed even if wgsl_files is empty, because C++ files might contain embedded WGSL
+
+ validate_uniforms(wgsl_files, cpp_files)
+
+if __name__ == "__main__":
+ main() \ No newline at end of file