Initial commit: VKZip GPU Compressor

This commit is contained in:
2026-04-20 23:19:07 -03:00
commit 7af9f38181
66 changed files with 9444 additions and 0 deletions
+850
View File
@@ -0,0 +1,850 @@
/*
* ╔══════════════════════════════════════════════════════════════════╗
* ║ VKZip v1.0 ║
* ║ GPU-Accelerated File Compressor/Decompressor ║
* ║ Powered by Vulkan Compute Shaders ║
* ╚══════════════════════════════════════════════════════════════════╝
*
* Usage:
* vkzip compress <input_file> [output.vkz]
* vkzip decompress <archive.vkz> [output_file]
* vkzip info <archive.vkz>
* vkzip benchmark <input_file>
* 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 <errno.h>
#include <sys/stat.h>
// ── 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
" <input_file> [output.vkz] Compress a file\n");
printf(" vkzip " COL_GREEN "decompress" COL_RESET
" <archive.vkz> [output_file] Decompress archive\n");
printf(" vkzip " COL_GREEN "info" COL_RESET
" <archive.vkz> Show archive info\n");
printf(" vkzip " COL_GREEN "benchmark" COL_RESET
" <input_file> 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--;
}
}
// ── 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];
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);
}
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) {
Timer total_timer;
timer_start(&total_timer);
// Read archive
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 <input_file> [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 <archive.vkz> [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 <archive.vkz>");
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 <input_file>");
return 1;
}
return cmd_benchmark(argv[2]);
}
LOG_ERR("Unknown command: %s", command);
LOG_ERR("Run 'vkzip --help' for usage information");
return 1;
}