volviewer

Volumetric Data Toy Viewer
git clone anongit@rnpnr.xyz:volviewer.git
Log | Files | Refs | Feed | LICENSE

build.c (12226B)


      1 /* See LICENSE for license details. */
      2 /* NOTE: inspired by nob: https://github.com/tsoding/nob.h */
      3 
      4 /* TODO(rnp):
      5  * [ ]: bake shaders and font data into binary
      6  *      - for shaders there is a way of making a separate data section and referring
      7  *        to it with extern from the C source (bake both data and size)
      8  *      - use objcopy, maybe need linker script maybe command line flags for ld will work
      9  * [ ]: cross compile/override baked compiler
     10  */
     11 
     12 #define COMMON_FLAGS "-std=c11", "-Wall"
     13 
     14 #define OUTDIR    "out"
     15 #define OUTPUT(s) OUTDIR "/" s
     16 
     17 #include "compiler.h"
     18 #include "util.h"
     19 
     20 #include "options.h"
     21 
     22 #include <stdarg.h>
     23 #include <stdio.h>
     24 
     25 #define is_aarch64 ARCH_ARM64
     26 #define is_amd64   ARCH_X64
     27 #define is_unix    OS_LINUX
     28 #define is_w32     OS_WINDOWS
     29 #define is_clang   COMPILER_CLANG
     30 
     31 #if OS_LINUX
     32 
     33   #include <errno.h>
     34   #include <string.h>
     35   #include <sys/select.h>
     36   #include <sys/wait.h>
     37 
     38   #include "os_linux.c"
     39 
     40   #define OS_MAIN "main_linux.c"
     41 
     42 #elif OS_WINDOWS
     43 
     44   #include "os_win32.c"
     45 
     46   #define OS_MAIN "main_w32.c"
     47 
     48 #else
     49   #error Unsupported Platform
     50 #endif
     51 
     52 #if COMPILER_CLANG
     53   #define COMPILER "clang"
     54 #elif COMPILER_MSVC
     55   #define COMPILER "cl"
     56 #else
     57   #define COMPILER "cc"
     58 #endif
     59 
     60 #define shift(list, count) ((count)--, *(list)++)
     61 
     62 #define da_append_count(a, s, items, item_count) do { \
     63 	da_reserve((a), (s), (s)->count + (item_count));                            \
     64 	mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (item_count)); \
     65 	(s)->count += (item_count);                                                 \
     66 } while (0)
     67 
     68 #define cmd_append_count da_append_count
     69 #define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \
     70                                              (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
     71 
     72 typedef struct {
     73 	char **data;
     74 	sz     count;
     75 	sz     capacity;
     76 } CommandList;
     77 
     78 typedef struct {
     79 	b32   debug;
     80 	b32   generic;
     81 	b32   report;
     82 	b32   sanitize;
     83 
     84 	b32   encode_video;
     85 	c8   *video_name;
     86 } Options;
     87 
     88 #define die(fmt, ...) die_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
     89 function void __attribute__((noreturn))
     90 die_(char *format, ...)
     91 {
     92 	va_list ap;
     93 	va_start(ap, format);
     94 	/* TODO(rnp): proper log */
     95 	vfprintf(stderr, format, ap);
     96 	va_end(ap);
     97 	os_fatal(str8(""));
     98 }
     99 
    100 function b32
    101 str8_contains(str8 s, u8 byte)
    102 {
    103 	b32 result = 0;
    104 	for (sz i = 0 ; !result && i < s.len; i++)
    105 		result |= s.data[i] == byte;
    106 	return result;
    107 }
    108 
    109 function void
    110 stream_push_command(Stream *s, CommandList *c)
    111 {
    112 	if (!s->errors) {
    113 		for (sz i = 0; i < c->count; i++) {
    114 			str8 item = c_str_to_str8(c->data[i]);
    115 			if (item.len) {
    116 				b32 escape = str8_contains(item, ' ') || str8_contains(item, '"');
    117 				if (escape) stream_append_byte(s, '\'');
    118 				stream_append_str8(s, item);
    119 				if (escape) stream_append_byte(s, '\'');
    120 				if (i != c->count - 1) stream_append_byte(s, ' ');
    121 			}
    122 		}
    123 	}
    124 }
    125 
    126 #if OS_LINUX
    127 
    128 function b32
    129 os_rename_file(char *name, char *new)
    130 {
    131 	b32 result = rename(name, new) != -1;
    132 	return result;
    133 }
    134 
    135 function b32
    136 os_remove_file(char *name)
    137 {
    138 	b32 result = remove(name) != -1;
    139 	return result;
    140 }
    141 
    142 function u64
    143 os_get_filetime(char *file)
    144 {
    145 	struct stat sb;
    146 	u64 result = (u64)-1;
    147 	if (stat(file, &sb) != -1)
    148 		result = sb.st_mtim.tv_sec;
    149 	return result;
    150 }
    151 
    152 function sptr
    153 os_spawn_process(CommandList *cmd, Stream sb)
    154 {
    155 	pid_t result = fork();
    156 	switch (result) {
    157 	case -1: die("failed to fork command: %s: %s\n", cmd->data[0], strerror(errno)); break;
    158 	case  0: {
    159 		if (execvp(cmd->data[0], cmd->data) == -1)
    160 			die("failed to exec command: %s: %s\n", cmd->data[0], strerror(errno));
    161 		unreachable();
    162 	} break;
    163 	}
    164 	return (sptr)result;
    165 }
    166 
    167 function b32
    168 os_wait_close_process(sptr handle)
    169 {
    170 	b32 result = 0;
    171 	for (;;) {
    172 		s32  status;
    173 		sptr wait_pid = (sptr)waitpid(handle, &status, 0);
    174 		if (wait_pid == -1)
    175 			die("failed to wait on child process: %s\n", strerror(errno));
    176 		if (wait_pid == handle) {
    177 			if (WIFEXITED(status)) {
    178 				status = WEXITSTATUS(status);
    179 				/* TODO(rnp): logging */
    180 				result = status == 0;
    181 				break;
    182 			}
    183 			if (WIFSIGNALED(status)) {
    184 				/* TODO(rnp): logging */
    185 				result = 0;
    186 				break;
    187 			}
    188 		} else {
    189 			/* TODO(rnp): handle multiple children */
    190 			InvalidCodePath;
    191 		}
    192 	}
    193 	return result;
    194 }
    195 
    196 #elif OS_WINDOWS
    197 
    198 enum {
    199 	MOVEFILE_REPLACE_EXISTING = 0x01,
    200 };
    201 
    202 W32(b32) CreateProcessA(u8 *, u8 *, sptr, sptr, b32, u32, sptr, u8 *, sptr, sptr);
    203 W32(b32) DeleteFileA(c8 *);
    204 W32(b32) GetExitCodeProcess(sptr handle, u32 *);
    205 W32(b32) GetFileTime(sptr, sptr, sptr, sptr);
    206 W32(b32) MoveFileExA(c8 *, c8 *, u32);
    207 W32(u32) WaitForSingleObject(sptr, u32);
    208 
    209 function b32
    210 os_rename_file(char *name, char *new)
    211 {
    212 	b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0;
    213 	return result;
    214 }
    215 
    216 function b32
    217 os_remove_file(char *name)
    218 {
    219 	b32 result = DeleteFileA(name);
    220 	return result;
    221 }
    222 
    223 function u64
    224 os_get_filetime(char *file)
    225 {
    226 	u64 result = (u64)-1;
    227 	sptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    228 	if (h != INVALID_FILE) {
    229 		struct { u32 low, high; } w32_filetime;
    230 		GetFileTime(h, 0, 0, (sptr)&w32_filetime);
    231 		result = (u64)w32_filetime.high << 32ULL | w32_filetime.low;
    232 		CloseHandle(h);
    233 	}
    234 	return result;
    235 }
    236 
    237 function sptr
    238 os_spawn_process(CommandList *cmd, Stream sb)
    239 {
    240 	struct {
    241 		u32 cb;
    242 		u8 *reserved, *desktop, *title;
    243 		u32 x, y, x_size, y_size, x_count_chars, y_count_chars;
    244 		u32 fill_attr, flags;
    245 		u16 show_window, reserved_2;
    246 		u8 *reserved_3;
    247 		sptr std_input, std_output, std_error;
    248 	} w32_startup_info = {
    249 		.cb = sizeof(w32_startup_info),
    250 		.flags = 0x100,
    251 		.std_input  = GetStdHandle(STD_INPUT_HANDLE),
    252 		.std_output = GetStdHandle(STD_OUTPUT_HANDLE),
    253 		.std_error  = GetStdHandle(STD_ERROR_HANDLE),
    254 	};
    255 
    256 	struct {
    257 		sptr phandle, thandle;
    258 		u32  pid, tid;
    259 	} w32_process_info = {0};
    260 
    261 	/* TODO(rnp): warn if we need to clamp last string */
    262 	sb.widx = MIN(sb.widx, KB(32) - 1);
    263 	if (sb.widx < sb.cap) sb.data[sb.widx]     = 0;
    264 	else                  sb.data[sb.widx - 1] = 0;
    265 
    266 	sptr result = INVALID_FILE;
    267 	if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (sptr)&w32_startup_info,
    268 	                   (sptr)&w32_process_info))
    269 	{
    270 		CloseHandle(w32_process_info.thandle);
    271 		result = w32_process_info.phandle;
    272 	}
    273 	return result;
    274 }
    275 
    276 function b32
    277 os_wait_close_process(sptr handle)
    278 {
    279 	b32 result = WaitForSingleObject(handle, -1) != 0xFFFFFFFFUL;
    280 	if (result) {
    281 		u32 status;
    282 		GetExitCodeProcess(handle, &status);
    283 		result = status == 0;
    284 	}
    285 	CloseHandle(handle);
    286 	return result;
    287 }
    288 
    289 #endif
    290 
    291 #define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \
    292                                              (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
    293 function b32
    294 needs_rebuild_(char *binary, char *deps[], sz deps_count)
    295 {
    296 	u64 binary_filetime = os_get_filetime(binary);
    297 	b32 result = binary_filetime == (u64)-1;
    298 	for (sz i = 0; i < deps_count; i++) {
    299 		u64 filetime = os_get_filetime(deps[i]);
    300 		result |= (filetime == (u64)-1) | (filetime > binary_filetime);
    301 	}
    302 	return result;
    303 }
    304 
    305 function b32
    306 run_synchronous(Arena a, CommandList *command)
    307 {
    308 	Stream sb = arena_stream(a);
    309 	stream_push_command(&sb, command);
    310 	printf("%.*s\n", (s32)sb.widx, sb.data);
    311 	return os_wait_close_process(os_spawn_process(command, sb));
    312 }
    313 
    314 function void
    315 check_rebuild_self(Arena arena, s32 argc, char *argv[])
    316 {
    317 	char *binary = shift(argv, argc);
    318 	if (needs_rebuild(binary, __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h", "options.h")) {
    319 		Stream name_buffer = arena_stream(arena);
    320 		stream_append_str8s(&name_buffer, c_str_to_str8(binary), str8(".old"));
    321 		char *old_name = (char *)arena_stream_commit_zero(&arena, &name_buffer).data;
    322 
    323 		if (!os_rename_file(binary, old_name))
    324 			die("failed to move: %s -> %s\n", binary, old_name);
    325 
    326 		CommandList c = {0};
    327 		cmd_append(&arena, &c, COMPILER, "-march=native", "-O3", COMMON_FLAGS);
    328 		if (is_w32 && is_clang) cmd_append(&arena, &c, "-fms-extensions");
    329 		cmd_append(&arena, &c, "-Wno-unused-function", __FILE__, "-o", binary, (void *)0);
    330 		if (!run_synchronous(arena, &c)) {
    331 			os_rename_file(old_name, binary);
    332 			die("failed to rebuild self\n");
    333 		}
    334 		os_remove_file(old_name);
    335 
    336 		c.count = 0;
    337 		cmd_append(&arena, &c, binary);
    338 		cmd_append_count(&arena, &c, argv, argc);
    339 		cmd_append(&arena, &c, (void *)0);
    340 		if (!run_synchronous(arena, &c))
    341 			os_exit(1);
    342 
    343 		os_exit(0);
    344 	}
    345 }
    346 
    347 function b32
    348 str8_equal(str8 a, str8 b)
    349 {
    350 	b32 result = a.len == b.len;
    351 	for (sz i = 0; result && i < a.len; i++)
    352 		result = a.data[i] == b.data[i];
    353 	return result;
    354 }
    355 
    356 function void
    357 usage(char *argv0)
    358 {
    359 	die("%s [--debug] [--report] [--sanitize] [--encode-video 'output']\n"
    360 	    "    --debug:         dynamically link and build with debug symbols\n"
    361 	    "    --generic:       compile for a generic target (x86-64-v3 or armv8 with NEON)\n"
    362 	    "    --report:        print compilation stats (clang only)\n"
    363 	    "    --sanitize:      build with ASAN and UBSAN\n"
    364 	    "    --encode-video:  encode '" RAW_OUTPUT_PATH "' to 'output'\n"
    365 	    , argv0);
    366 }
    367 
    368 function Options
    369 parse_options(s32 argc, char *argv[])
    370 {
    371 	Options result = {0};
    372 
    373 	char *argv0 = shift(argv, argc);
    374 	while (argc > 0) {
    375 		char *arg = shift(argv, argc);
    376 		str8 str    = c_str_to_str8(arg);
    377 		if (str8_equal(str, str8("--debug"))) {
    378 			result.debug = 1;
    379 		} else if (str8_equal(str, str8("--encode-video"))) {
    380 			result.encode_video = 1;
    381 			if (argc) result.video_name = shift(argv, argc);
    382 			else      usage(argv0);
    383 		} else if (str8_equal(str, str8("--generic"))) {
    384 			result.generic = 1;
    385 		} else if (str8_equal(str, str8("--report"))) {
    386 			result.report = 1;
    387 		} else if (str8_equal(str, str8("--sanitize"))) {
    388 			result.sanitize = 1;
    389 		} else {
    390 			usage(argv0);
    391 		}
    392 	}
    393 
    394 	return result;
    395 }
    396 
    397 function CommandList
    398 cmd_base(Arena *a, Options *o)
    399 {
    400 	CommandList result = {0};
    401 	cmd_append(a, &result, COMPILER);
    402 
    403 	/* TODO(rnp): support cross compiling with clang */
    404 	if (!o->generic)     cmd_append(a, &result, "-march=native");
    405 	else if (is_amd64)   cmd_append(a, &result, "-march=x86-64-v3");
    406 	else if (is_aarch64) cmd_append(a, &result, "-march=armv8");
    407 
    408 	cmd_append(a, &result, COMMON_FLAGS);
    409 
    410 	if (o->debug) cmd_append(a, &result, "-O0", "-D_DEBUG", "-Wno-unused-function");
    411 	else          cmd_append(a, &result, "-O3");
    412 
    413 	if (is_w32 && is_clang) cmd_append(a, &result, "-fms-extensions");
    414 
    415 	if (o->debug && is_unix) cmd_append(a, &result, "-gdwarf-4");
    416 
    417 	if (o->sanitize) cmd_append(a, &result, "-fsanitize=address,undefined");
    418 
    419 	if (o->report) {
    420 		if (is_clang) cmd_append(a, &result, "-fproc-stat-report");
    421 		else printf("warning: timing not supported with this compiler\n");
    422 		/* TODO(rnp): basic timing */
    423 	}
    424 
    425 	return result;
    426 }
    427 
    428 /* NOTE(rnp): produce pdbs on w32 */
    429 function void
    430 cmd_pdb(Arena *a, CommandList *cmd)
    431 {
    432 	if (is_w32 && is_clang)
    433 		cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb=");
    434 }
    435 
    436 /* NOTE(rnp): gcc requires these to appear at the end for no reason at all */
    437 function void
    438 cmd_append_ldflags(Arena *a, CommandList *cc, b32 shared)
    439 {
    440 	cmd_pdb(a, cc);
    441 	if (is_w32)  cmd_append(a, cc, "-lopengl32", "-lgdi32", "-lwinmm");
    442 	if (is_unix) cmd_append(a, cc, "-lGL");
    443 }
    444 
    445 function CommandList
    446 cmd_encode_video(Arena *a, c8 *output_name)
    447 {
    448 	CommandList result = {0};
    449 	cmd_append(a, &result, "ffmpeg", "-y",
    450 	           "-framerate", str(OUTPUT_FRAME_RATE),
    451 	           "-f",         "rawvideo",
    452 	           "-pix_fmt",   "abgr",
    453 	           "-s:v",       str(RENDER_TARGET_WIDTH) "x" str(RENDER_TARGET_HEIGHT),
    454 	           "-i",         RAW_OUTPUT_PATH,
    455 	           "-c:v",       "libx265",
    456 	           "-crf",       "22",
    457 	           output_name, (void *)0);
    458 	return result;
    459 }
    460 
    461 extern s32
    462 main(s32 argc, char *argv[])
    463 {
    464 	Arena arena = os_alloc_arena(MB(8));
    465 	check_rebuild_self(arena, argc, argv);
    466 
    467 	Options options = parse_options(argc, argv);
    468 
    469 	CommandList c;
    470 	if (!options.encode_video) {
    471 		c = cmd_base(&arena, &options);
    472 		if (is_unix) cmd_append(&arena, &c, "-D_GLFW_X11");
    473 		cmd_append(&arena, &c, "-Iexternal/glfw/include");
    474 
    475 		cmd_append(&arena, &c, OS_MAIN, "external/rglfw.c", "-o", "volviewer");
    476 		cmd_append_ldflags(&arena, &c, options.debug);
    477 		cmd_append(&arena, &c, (void *)0);
    478 	} else {
    479 		c = cmd_encode_video(&arena, options.video_name);
    480 	}
    481 
    482 	return !run_synchronous(arena, &c);
    483 }