943 lines
32 KiB
C
943 lines
32 KiB
C
/*
|
|
* ╔══════════════════════════════════════════════════════════════════╗
|
|
* ║ 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>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <process.h>
|
|
#define F_OK 0
|
|
#define W_OK 2
|
|
#define R_OK 4
|
|
#define access _access
|
|
#define unlink _unlink
|
|
#define getpid _getpid
|
|
#else
|
|
#include <unistd.h>
|
|
#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
|
|
" <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--;
|
|
}
|
|
}
|
|
|
|
// ── 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 <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;
|
|
}
|