Initial commit: VKZip GPU Compressor
This commit is contained in:
+850
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user