/* * ╔══════════════════════════════════════════════════════════════════╗ * ║ VKZip v1.0 ║ * ║ GPU-Accelerated File Compressor/Decompressor ║ * ║ Powered by Vulkan Compute Shaders ║ * ╚══════════════════════════════════════════════════════════════════╝ * * Usage: * vkzip compress [output.vkz] * vkzip decompress [output_file] * vkzip info * vkzip benchmark * vkzip --help * * Cross-platform: Linux & Windows * GPU Support: Any Vulkan-capable GPU (NVIDIA, AMD, Intel) * Fallback: CPU compression when no GPU is available */ #include "cpu_fallback.h" #include "gpu_compress.h" #include "gpu_context.h" #include "gpu_decompress.h" #include "utils.h" #include "vkz_format.h" #include #include #ifdef _WIN32 #include #include #define F_OK 0 #define W_OK 2 #define R_OK 4 #define access _access #define unlink _unlink #define getpid _getpid #else #include #endif // ── Banner ───────────────────────────────────────────────────────── static void print_banner(void) { printf("\n"); printf(COL_BOLD COL_GREEN " ██╗ ██╗██╗ ██╗███████╗██╗██████╗ \n" " ██║ ██║██║ ██╔╝╚══███╔╝██║██╔══██╗\n" " ██║ ██║█████╔╝ ███╔╝ ██║██████╔╝\n" " ╚██╗ ██╔╝██╔═██╗ ███╔╝ ██║██╔═══╝ \n" " ╚████╔╝ ██║ ██╗███████╗██║██║ \n" " ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝╚═╝ \n" COL_RESET); printf(COL_CYAN " GPU-Accelerated File Compressor v1.0\n" COL_RESET); printf(COL_CYAN " Powered by Vulkan Compute Shaders\n" COL_RESET); printf("\n"); } // ── Help ─────────────────────────────────────────────────────────── static void print_help(void) { print_banner(); printf("Usage:\n"); printf(" vkzip " COL_GREEN "compress" COL_RESET " [output.vkz] Compress a file\n"); printf(" vkzip " COL_GREEN "decompress" COL_RESET " [output_file] Decompress archive\n"); printf(" vkzip " COL_GREEN "info" COL_RESET " Show archive info\n"); printf(" vkzip " COL_GREEN "benchmark" COL_RESET " Benchmark GPU vs CPU\n"); printf(" vkzip " COL_GREEN "--gpu-info" COL_RESET " Show GPU information\n"); printf(" vkzip " COL_GREEN "--help" COL_RESET " Show this help\n"); printf("\n"); printf("Options:\n"); printf(" --cpu-only Force CPU mode (no GPU)\n"); printf(" --block-size Block size in KB (default: 64)\n"); printf("\n"); printf("Examples:\n"); printf(" vkzip compress myfile.bin\n"); printf(" vkzip compress myfile.bin output.vkz\n"); printf(" vkzip decompress output.vkz\n"); printf(" vkzip decompress output.vkz restored.bin\n"); printf(" vkzip benchmark largefile.dat\n"); printf("\n"); } // ── Find shader path ─────────────────────────────────────────────── static int find_shader_path(char *out_path, size_t max_len, const char *shader_name) { // Try build directory first (defined by CMake) #ifdef SHADER_DIR snprintf(out_path, max_len, "%s/%s", SHADER_DIR, shader_name); if (access(out_path, R_OK) == 0) return 0; #endif // Try relative to executable snprintf(out_path, max_len, "shaders/%s", shader_name); if (access(out_path, R_OK) == 0) return 0; // Try installed location snprintf(out_path, max_len, "/usr/share/vkzip/shaders/%s", shader_name); if (access(out_path, R_OK) == 0) return 0; // Try local share snprintf(out_path, max_len, "/usr/local/share/vkzip/shaders/%s", shader_name); if (access(out_path, R_OK) == 0) return 0; return -1; } // ── Check if path is a directory ─────────────────────────────────── static bool is_directory(const char *path) { struct stat st; if (stat(path, &st) != 0) return false; return S_ISDIR(st.st_mode); } // ── Strip trailing slashes ───────────────────────────────────────── static void strip_trailing_slash(char *path) { size_t len = strlen(path); while (len > 1 && (path[len - 1] == '/' || path[len - 1] == '\\')) { path[len - 1] = '\0'; len--; } } // ── Check file extension ─────────────────────────────────────────── static bool ends_with(const char *str, const char *suffix) { if (!str || !suffix) return false; size_t lenstr = strlen(str); size_t lensuffix = strlen(suffix); if (lensuffix > lenstr) return false; return strcasecmp(str + lenstr - lensuffix, suffix) == 0; } // ── Extract external archives via system ─────────────────────────── static int extract_external_archive(const char *input_path, const char *output_dir, const char *type) { char cmd[VKZ_MAX_FILENAME * 3]; // Create output dir if specified if (output_dir) { #ifdef _WIN32 snprintf(cmd, sizeof(cmd), "if not exist \"%s\" mkdir \"%s\"", output_dir, output_dir); #else snprintf(cmd, sizeof(cmd), "mkdir -p '%s'", output_dir); #endif system(cmd); } const char *out_target = output_dir ? output_dir : "."; if (strcmp(type, "zip") == 0) { LOG_INFO("Extracting ZIP archive using system tools..."); #ifdef _WIN32 snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", input_path, out_target); #else snprintf(cmd, sizeof(cmd), "unzip -q -o '%s' -d '%s'", input_path, out_target); #endif } else if (strcmp(type, "rar") == 0) { LOG_INFO("Extracting RAR archive using system tools..."); #ifdef _WIN32 snprintf(cmd, sizeof(cmd), "unrar x -y \"%s\" \"%s\\\"", input_path, out_target); #else snprintf(cmd, sizeof(cmd), "unrar x -y '%s' '%s/'", input_path, out_target); #endif } else { return -1; } Timer timer; timer_start(&timer); if (system(cmd) == 0) { double time = timer_stop(&timer); LOG_OK("Extraction complete! (%.3f seconds)", time); return 0; } else { LOG_ERR("Extraction failed. Do you have %s installed?", type); return 1; } } // ── Create tar from directory ────────────────────────────────────── static char *tar_directory(const char *dir_path) { static char tar_path[VKZ_MAX_FILENAME]; snprintf(tar_path, sizeof(tar_path), "/tmp/vkzip_%u.tar", (uint32_t)getpid()); // Get parent dir and basename char dir_copy[VKZ_MAX_FILENAME]; strncpy(dir_copy, dir_path, sizeof(dir_copy) - 1); strip_trailing_slash(dir_copy); char *last_slash = strrchr(dir_copy, '/'); char parent[VKZ_MAX_FILENAME] = "."; const char *basename = dir_copy; if (last_slash) { *last_slash = '\0'; strncpy(parent, dir_copy, sizeof(parent) - 1); basename = last_slash + 1; } char cmd[VKZ_MAX_FILENAME * 3]; snprintf(cmd, sizeof(cmd), "tar cf '%s' -C '%s' '%s' 2>/dev/null", tar_path, parent, basename); LOG_INFO("Packing directory with tar..."); int ret = system(cmd); if (ret != 0) { LOG_ERR("Failed to create tar archive from directory"); return NULL; } char size_buf[32]; format_size(get_file_size(tar_path), size_buf, sizeof(size_buf)); LOG_OK("Directory packed: %s", size_buf); return tar_path; } // ── Extract tar to directory ─────────────────────────────────────── static int untar_file(const char *tar_path, const char *output_dir) { // Create output directory if needed char cmd[VKZ_MAX_FILENAME * 3]; #ifdef _WIN32 if (output_dir) { snprintf(cmd, sizeof(cmd), "if not exist \"%s\" mkdir \"%s\" && tar xf \"%s\" -C \"%s\"", output_dir, output_dir, tar_path, output_dir); } else { snprintf(cmd, sizeof(cmd), "tar xf \"%s\"", tar_path); } #else if (output_dir) { snprintf(cmd, sizeof(cmd), "mkdir -p '%s' && tar xf '%s' -C '%s' 2>/dev/null", output_dir, tar_path, output_dir); } else { snprintf(cmd, sizeof(cmd), "tar xf '%s' 2>/dev/null", tar_path); } #endif int ret = system(cmd); return (ret == 0) ? 0 : -1; } // ── Generate output filename ─────────────────────────────────────── static void make_compress_output(const char *input, char *output, size_t max_len) { // Strip trailing slash for directories char clean[VKZ_MAX_FILENAME]; strncpy(clean, input, sizeof(clean) - 1); strip_trailing_slash(clean); snprintf(output, max_len, "%s.vkz", clean); } static void make_decompress_output(const VkzArchive *archive, char *output, size_t max_len) { if (archive->original_filename[0] != '\0') { snprintf(output, max_len, "%s", archive->original_filename); } else { snprintf(output, max_len, "output.bin"); } } // ── Compress command ─────────────────────────────────────────────── static int cmd_compress(const char *input_path, const char *output_path, bool cpu_only, uint32_t block_size_kb) { Timer total_timer; timer_start(&total_timer); // Check if input is a directory bool is_dir = is_directory(input_path); const char *actual_input = input_path; char *tar_temp_path = NULL; if (is_dir) { LOG_INFO("Input is a directory: %s", input_path); tar_temp_path = tar_directory(input_path); if (!tar_temp_path) return 1; actual_input = tar_temp_path; } // Read input file uint64_t input_size = get_file_size(actual_input); if (input_size == 0) { LOG_ERR("Cannot read file or file is empty: %s", actual_input); if (tar_temp_path) unlink(tar_temp_path); return 1; } char size_buf[32]; format_size(input_size, size_buf, sizeof(size_buf)); LOG_INFO("Input: %s (%s)", input_path, size_buf); if (is_dir) LOG_INFO("Mode: directory (tar + compress)"); FILE *f = fopen(actual_input, "rb"); if (!f) { LOG_ERR("Cannot open input file: %s", input_path); return 1; } uint8_t *input_data = (uint8_t *)malloc((size_t)input_size); if (!input_data) { LOG_ERR("Out of memory (need %s)", size_buf); fclose(f); if (tar_temp_path) unlink(tar_temp_path); return 1; } fread(input_data, 1, (size_t)input_size, f); fclose(f); uint32_t block_size = block_size_kb * 1024; // Auto-generate output filename if needed char auto_output[VKZ_MAX_FILENAME]; if (!output_path) { make_compress_output(input_path, auto_output, sizeof(auto_output)); output_path = auto_output; } LOG_INFO("Output: %s", output_path); LOG_INFO("Block size: %u KB", block_size_kb); // ── Try GPU compression ──────────────────────────────────────── uint8_t **compressed_blocks = NULL; uint32_t *block_sizes = NULL; uint32_t block_count = 0; bool used_gpu = false; if (!cpu_only) { GpuContext gpu; if (gpu_init(&gpu) == 0) { gpu_print_info(&gpu); // Load compression shader char shader_path[1024]; if (find_shader_path(shader_path, sizeof(shader_path), "compress.comp.spv") == 0) { gpu.compress_shader = gpu_load_shader(&gpu, shader_path); if (gpu.compress_shader != VK_NULL_HANDLE) { GpuCompressPipeline pipe; if (gpu_compress_init(&pipe, &gpu) == 0) { LOG_INFO("Compressing with GPU..."); Timer gpu_timer; timer_start(&gpu_timer); int result = gpu_compress_data(&pipe, input_data, input_size, block_size, &compressed_blocks, &block_sizes, &block_count); double gpu_time = timer_stop(&gpu_timer); if (result == 0) { used_gpu = true; LOG_OK("GPU compression done in %.3f seconds", gpu_time); double throughput = (double)input_size / (1024.0 * 1024.0) / gpu_time; LOG_OK("Throughput: %.1f MB/s", throughput); } else { LOG_WARN("GPU compression failed, falling back to CPU"); } gpu_compress_cleanup(&pipe); } } else { LOG_WARN("Could not load compression shader"); } } else { LOG_WARN("Shader file not found, falling back to CPU"); } gpu_cleanup(&gpu); } else { LOG_WARN("No Vulkan GPU available, using CPU fallback"); } } // ── CPU fallback ─────────────────────────────────────────────── if (!used_gpu) { LOG_INFO("Compressing with CPU..."); Timer cpu_timer; timer_start(&cpu_timer); int result = cpu_compress_file(input_data, input_size, block_size, &compressed_blocks, &block_sizes, &block_count); double cpu_time = timer_stop(&cpu_timer); if (result != 0) { LOG_ERR("CPU compression failed!"); free(input_data); return 1; } LOG_OK("CPU compression done in %.3f seconds", cpu_time); double throughput = (double)input_size / (1024.0 * 1024.0) / cpu_time; LOG_OK("Throughput: %.1f MB/s", throughput); } // ── Build and write archive ──────────────────────────────────── VkzArchive *archive = vkz_create(input_size, block_size, input_path); if (!archive) { LOG_ERR("Failed to create archive structure"); free(input_data); return 1; } // Set block compressed sizes and checksums for (uint32_t i = 0; i < block_count; i++) { archive->blocks[i].compressed_size = block_sizes[i]; archive->blocks[i].checksum = vkz_crc32(compressed_blocks[i], block_sizes[i]); } // Set directory flag if input was a directory if (is_dir) { archive->header.flags |= VKZ_FLAG_DIRECTORY; } // Set overall checksum archive->header.checksum = vkz_crc32(input_data, (size_t)input_size); int write_result = vkz_write(archive, (const uint8_t **)compressed_blocks, output_path); // Print results if (write_result == 0) { vkz_print_info(archive); double total_time = timer_stop(&total_timer); uint64_t output_file_size = get_file_size(output_path); char out_buf[32]; format_size(output_file_size, out_buf, sizeof(out_buf)); printf(COL_BOLD COL_GREEN "✓ Compression complete!" COL_RESET "\n"); printf(" Method: %s\n", used_gpu ? "GPU (Vulkan)" : "CPU"); printf(" Time: %.3f seconds\n", total_time); printf(" Output: %s (%s)\n", output_path, out_buf); printf("\n"); } // Cleanup for (uint32_t i = 0; i < block_count; i++) { free(compressed_blocks[i]); } free(compressed_blocks); free(block_sizes); free(input_data); vkz_free(archive); // Remove temp tar if (tar_temp_path) unlink(tar_temp_path); return write_result; } // ── Decompress command ───────────────────────────────────────────── static int cmd_decompress(const char *archive_path, const char *output_path, bool cpu_only) { // Check for standard external archives (zip, rar) if (ends_with(archive_path, ".zip")) { return extract_external_archive(archive_path, output_path, "zip"); } else if (ends_with(archive_path, ".rar")) { return extract_external_archive(archive_path, output_path, "rar"); } // Native VKZ decompression Timer total_timer; timer_start(&total_timer); VkzArchive *archive = vkz_read(archive_path); if (!archive) { return 1; } vkz_print_info(archive); // Generate output path if not provided char auto_output[VKZ_MAX_FILENAME]; if (!output_path) { make_decompress_output(archive, auto_output, sizeof(auto_output)); output_path = auto_output; } LOG_INFO("Output: %s", output_path); // Allocate output buffer uint8_t *output_data = (uint8_t *)calloc(1, (size_t)archive->header.original_size); if (!output_data) { char buf[32]; format_size(archive->header.original_size, buf, sizeof(buf)); LOG_ERR("Out of memory (need %s)", buf); vkz_free(archive); return 1; } bool used_gpu = false; // ── Try GPU decompression ────────────────────────────────────── if (!cpu_only) { GpuContext gpu; if (gpu_init(&gpu) == 0) { char shader_path[1024]; if (find_shader_path(shader_path, sizeof(shader_path), "decompress.comp.spv") == 0) { gpu.decompress_shader = gpu_load_shader(&gpu, shader_path); if (gpu.decompress_shader != VK_NULL_HANDLE) { GpuDecompressPipeline pipe; if (gpu_decompress_init(&pipe, &gpu) == 0) { LOG_INFO("Decompressing with GPU..."); Timer gpu_timer; timer_start(&gpu_timer); int result = gpu_decompress_data(&pipe, archive, archive_path, output_data, archive->header.original_size); double gpu_time = timer_stop(&gpu_timer); if (result == 0) { used_gpu = true; LOG_OK("GPU decompression done in %.3f seconds", gpu_time); } else { LOG_WARN("GPU decompression failed, falling back to CPU"); } gpu_decompress_cleanup(&pipe); } } } gpu_cleanup(&gpu); } } // ── CPU fallback ─────────────────────────────────────────────── if (!used_gpu) { LOG_INFO("Decompressing with CPU..."); Timer cpu_timer; timer_start(&cpu_timer); int result = cpu_decompress_file(archive, archive_path, output_data, archive->header.original_size); double cpu_time = timer_stop(&cpu_timer); if (result != 0) { LOG_ERR("Decompression failed!"); free(output_data); vkz_free(archive); return 1; } LOG_OK("CPU decompression done in %.3f seconds", cpu_time); } // ── Verify checksum ──────────────────────────────────────────── uint32_t checksum = vkz_crc32(output_data, (size_t)archive->header.original_size); if (archive->header.checksum != 0 && checksum != archive->header.checksum) { LOG_WARN("CRC32 mismatch! File may be corrupted."); LOG_WARN("Expected: 0x%08X Got: 0x%08X", archive->header.checksum, checksum); } else if (archive->header.checksum != 0) { LOG_OK("CRC32 checksum verified: 0x%08X", checksum); } // ── Handle output ────────────────────────────────────────────── bool is_dir_archive = (archive->header.flags & VKZ_FLAG_DIRECTORY) != 0; if (is_dir_archive) { // Write tar to temp file, then extract char tar_path[VKZ_MAX_FILENAME]; snprintf(tar_path, sizeof(tar_path), "/tmp/vkzip_decomp_%u.tar", (uint32_t)getpid()); FILE *f = fopen(tar_path, "wb"); if (!f) { LOG_ERR("Cannot create temp tar: %s", tar_path); free(output_data); vkz_free(archive); return 1; } fwrite(output_data, 1, (size_t)archive->header.original_size, f); fclose(f); LOG_INFO("Extracting directory..."); // If output_path is specified, extract there; otherwise extract to current // dir int ret = untar_file(tar_path, output_path); unlink(tar_path); if (ret != 0) { LOG_ERR("Failed to extract directory from tar"); free(output_data); vkz_free(archive); return 1; } double total_time = timer_stop(&total_timer); char size_buf[32]; format_size(archive->header.original_size, size_buf, sizeof(size_buf)); printf(COL_BOLD COL_GREEN "✓ Directory extracted!" COL_RESET "\n"); printf(" Method: %s\n", used_gpu ? "GPU (Vulkan)" : "CPU"); printf(" Time: %.3f seconds\n", total_time); printf(" Output: %s\n", output_path ? output_path : "(current directory)"); printf(" Tar: %s\n", size_buf); printf("\n"); } else { // Regular file output FILE *f = fopen(output_path, "wb"); if (!f) { LOG_ERR("Cannot create output file: %s", output_path); free(output_data); vkz_free(archive); return 1; } fwrite(output_data, 1, (size_t)archive->header.original_size, f); fclose(f); double total_time = timer_stop(&total_timer); char size_buf[32]; format_size(archive->header.original_size, size_buf, sizeof(size_buf)); printf(COL_BOLD COL_GREEN "✓ Decompression complete!" COL_RESET "\n"); printf(" Method: %s\n", used_gpu ? "GPU (Vulkan)" : "CPU"); printf(" Time: %.3f seconds\n", total_time); printf(" Output: %s (%s)\n", output_path, size_buf); printf("\n"); } free(output_data); vkz_free(archive); return 0; } // ── Info command ─────────────────────────────────────────────────── static int cmd_info(const char *archive_path) { print_banner(); VkzArchive *archive = vkz_read(archive_path); if (!archive) return 1; vkz_print_info(archive); // Print block details printf(COL_BOLD "Block Details:\n" COL_RESET); printf(" %-6s %-12s %-12s %-8s %-10s\n", "Block", "Original", "Compressed", "Ratio", "CRC32"); printf(" ────── ──────────── ──────────── ──────── ──────────\n"); for (uint32_t i = 0; i < archive->header.block_count && i < 20; i++) { char orig_buf[32], comp_buf[32]; format_size(archive->blocks[i].original_size, orig_buf, sizeof(orig_buf)); format_size(archive->blocks[i].compressed_size, comp_buf, sizeof(comp_buf)); double ratio = archive->blocks[i].original_size > 0 ? (double)archive->blocks[i].compressed_size / (double)archive->blocks[i].original_size * 100.0 : 0.0; printf(" %-6u %-12s %-12s %5.1f%% 0x%08X\n", i, orig_buf, comp_buf, ratio, archive->blocks[i].checksum); } if (archive->header.block_count > 20) { printf(" ... and %u more blocks\n", archive->header.block_count - 20); } printf("\n"); vkz_free(archive); return 0; } // ── Benchmark command ────────────────────────────────────────────── static int cmd_benchmark(const char *input_path) { print_banner(); uint64_t input_size = get_file_size(input_path); if (input_size == 0) { LOG_ERR("Cannot read file: %s", input_path); return 1; } char size_buf[32]; format_size(input_size, size_buf, sizeof(size_buf)); LOG_INFO("Benchmarking: %s (%s)", input_path, size_buf); FILE *f = fopen(input_path, "rb"); if (!f) return 1; uint8_t *input_data = (uint8_t *)malloc((size_t)input_size); fread(input_data, 1, (size_t)input_size, f); fclose(f); uint32_t block_size = VKZ_BLOCK_SIZE; double cpu_time = 0, gpu_time = 0; uint64_t cpu_output_size = 0, gpu_output_size = 0; // ── CPU Benchmark ────────────────────────────────────────────── { LOG_INFO("Running CPU benchmark..."); uint8_t **blocks = NULL; uint32_t *sizes = NULL; uint32_t count = 0; Timer t; timer_start(&t); cpu_compress_file(input_data, input_size, block_size, &blocks, &sizes, &count); cpu_time = timer_stop(&t); for (uint32_t i = 0; i < count; i++) { cpu_output_size += sizes[i]; free(blocks[i]); } free(blocks); free(sizes); } // ── GPU Benchmark ────────────────────────────────────────────── { GpuContext gpu; if (gpu_init(&gpu) == 0) { gpu_print_info(&gpu); char shader_path[1024]; if (find_shader_path(shader_path, sizeof(shader_path), "compress.comp.spv") == 0) { gpu.compress_shader = gpu_load_shader(&gpu, shader_path); if (gpu.compress_shader != VK_NULL_HANDLE) { GpuCompressPipeline pipe; if (gpu_compress_init(&pipe, &gpu) == 0) { LOG_INFO("Running GPU benchmark..."); uint8_t **blocks = NULL; uint32_t *sizes = NULL; uint32_t count = 0; Timer t; timer_start(&t); gpu_compress_data(&pipe, input_data, input_size, block_size, &blocks, &sizes, &count); gpu_time = timer_stop(&t); for (uint32_t i = 0; i < count; i++) { gpu_output_size += sizes[i]; free(blocks[i]); } free(blocks); free(sizes); gpu_compress_cleanup(&pipe); } } } gpu_cleanup(&gpu); } else { LOG_WARN("No GPU available for benchmark"); } } // ── Results ──────────────────────────────────────────────────── printf("\n"); printf(COL_BOLD COL_YELLOW "╔══════════════════════════════════════════════════╗\n" COL_RESET); printf(COL_BOLD COL_YELLOW "║ Benchmark Results ║\n" COL_RESET); printf(COL_BOLD COL_YELLOW "╠══════════════════════════════════════════════════╣\n" COL_RESET); char cpu_buf[32], gpu_buf[32]; format_size(cpu_output_size, cpu_buf, sizeof(cpu_buf)); double cpu_ratio = input_size > 0 ? (double)cpu_output_size / (double)input_size * 100.0 : 0; double cpu_throughput = cpu_time > 0 ? (double)input_size / (1024.0 * 1024.0) / cpu_time : 0; printf(COL_YELLOW "║" COL_RESET " CPU:\n"); printf(COL_YELLOW "║" COL_RESET " Time: %.3f sec\n", cpu_time); printf(COL_YELLOW "║" COL_RESET " Output: %s (%.1f%%)\n", cpu_buf, cpu_ratio); printf(COL_YELLOW "║" COL_RESET " Throughput: %.1f MB/s\n", cpu_throughput); printf(COL_YELLOW "║" COL_RESET "\n"); if (gpu_time > 0) { format_size(gpu_output_size, gpu_buf, sizeof(gpu_buf)); double gpu_ratio = input_size > 0 ? (double)gpu_output_size / (double)input_size * 100.0 : 0; double gpu_throughput = gpu_time > 0 ? (double)input_size / (1024.0 * 1024.0) / gpu_time : 0; printf(COL_YELLOW "║" COL_RESET " GPU:\n"); printf(COL_YELLOW "║" COL_RESET " Time: %.3f sec\n", gpu_time); printf(COL_YELLOW "║" COL_RESET " Output: %s (%.1f%%)\n", gpu_buf, gpu_ratio); printf(COL_YELLOW "║" COL_RESET " Throughput: %.1f MB/s\n", gpu_throughput); printf(COL_YELLOW "║" COL_RESET "\n"); double speedup = cpu_time > 0 ? cpu_time / gpu_time : 0; printf(COL_YELLOW "║" COL_RESET COL_BOLD " Speedup: %.2fx %s\n" COL_RESET, speedup, speedup > 1.0 ? COL_GREEN "GPU wins! 🚀" COL_RESET : COL_RED "CPU wins" COL_RESET); } else { printf(COL_YELLOW "║" COL_RESET " GPU: " COL_RED "Not available" COL_RESET "\n"); } printf(COL_BOLD COL_YELLOW "╚══════════════════════════════════════════════════╝\n" COL_RESET); printf("\n"); free(input_data); return 0; } // ── GPU Info command ─────────────────────────────────────────────── static int cmd_gpu_info(void) { print_banner(); GpuContext gpu; if (gpu_init(&gpu) != 0) { LOG_ERR("No Vulkan GPU available"); return 1; } gpu_print_info(&gpu); gpu_cleanup(&gpu); return 0; } // ── Main ─────────────────────────────────────────────────────────── int main(int argc, char *argv[]) { if (argc < 2) { print_help(); return 0; } // Parse global options bool cpu_only = false; uint32_t block_size_kb = 64; // Check for flags anywhere in args for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--cpu-only") == 0) { cpu_only = true; } else if (strcmp(argv[i], "--block-size") == 0 && i + 1 < argc) { block_size_kb = (uint32_t)atoi(argv[++i]); if (block_size_kb < 4) block_size_kb = 4; if (block_size_kb > 1024) block_size_kb = 1024; } } const char *command = argv[1]; // ── Route commands ───────────────────────────────────────────── if (strcmp(command, "--help") == 0 || strcmp(command, "-h") == 0) { print_help(); return 0; } if (strcmp(command, "--gpu-info") == 0) { return cmd_gpu_info(); } if (strcmp(command, "compress") == 0 || strcmp(command, "c") == 0) { if (argc < 3) { LOG_ERR("Usage: vkzip compress [output.vkz]"); return 1; } print_banner(); const char *input = argv[2]; const char *output = (argc > 3 && argv[3][0] != '-') ? argv[3] : NULL; return cmd_compress(input, output, cpu_only, block_size_kb); } if (strcmp(command, "decompress") == 0 || strcmp(command, "d") == 0 || strcmp(command, "extract") == 0 || strcmp(command, "x") == 0) { if (argc < 3) { LOG_ERR("Usage: vkzip decompress [output_file]"); return 1; } print_banner(); const char *input = argv[2]; const char *output = (argc > 3 && argv[3][0] != '-') ? argv[3] : NULL; return cmd_decompress(input, output, cpu_only); } if (strcmp(command, "info") == 0 || strcmp(command, "i") == 0) { if (argc < 3) { LOG_ERR("Usage: vkzip info "); return 1; } return cmd_info(argv[2]); } if (strcmp(command, "benchmark") == 0 || strcmp(command, "bench") == 0 || strcmp(command, "b") == 0) { if (argc < 3) { LOG_ERR("Usage: vkzip benchmark "); return 1; } return cmd_benchmark(argv[2]); } LOG_ERR("Unknown command: %s", command); LOG_ERR("Run 'vkzip --help' for usage information"); return 1; }