Initial commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
// TaskForge build metaprogram.
|
||||
//
|
||||
// jai build.jai -> native Windows build into bin/
|
||||
// jai build.jai - release -> optimized native build (LLVM)
|
||||
// jai build.jai - web -> WASM64 build into web/
|
||||
// jai build.jai - server -> server for this OS into server/bin/ (optimized)
|
||||
// jai build.jai - server-linux -> Linux x64 server object file server/bin/taskforge-server.o,
|
||||
// linked on the droplet with: cc taskforge-server.o -o taskforge-server -lm -ldl -lpthread
|
||||
//
|
||||
// Every build also (re)bakes the font atlas from data/Karla-Regular.ttf using FreeType at compile time,
|
||||
// so the app itself never needs a font rasterizer at runtime (important for the browser build).
|
||||
|
||||
#run build();
|
||||
|
||||
build :: () {
|
||||
set_build_options_dc(.{do_output = false});
|
||||
|
||||
args := get_build_options().compile_time_command_line;
|
||||
web := array_find(args, "web");
|
||||
release := array_find(args, "release");
|
||||
|
||||
root := #filepath;
|
||||
|
||||
if array_find(args, "server") || array_find(args, "server-linux") {
|
||||
build_server(root, array_find(args, "server-linux"));
|
||||
return;
|
||||
}
|
||||
|
||||
font_bytes, ok := bake_fonts(tprint("%data/Karla-Regular.ttf", root));
|
||||
if !ok {
|
||||
compiler_report("Font baking failed.");
|
||||
return;
|
||||
}
|
||||
out_dir := ifx web then "web/" else "bin/";
|
||||
make_directory_if_it_does_not_exist(tprint("%1%2", root, out_dir));
|
||||
write_entire_file(tprint("%1%2fonts.bin", root, out_dir), font_bytes);
|
||||
|
||||
w := compiler_create_workspace(ifx web then "TaskForge Web" else "TaskForge");
|
||||
options := get_build_options(w);
|
||||
copy_commonly_propagated_fields(get_build_options(), *options);
|
||||
|
||||
options.output_path = out_dir;
|
||||
options.output_executable_name = ifx web then "taskforge" else "TaskForge";
|
||||
|
||||
import_paths: [..] string;
|
||||
array_add(*import_paths, tprint("%modules", root));
|
||||
for options.import_path array_add(*import_paths, it);
|
||||
options.import_path = import_paths;
|
||||
|
||||
if web {
|
||||
// Mirrors jai/examples/wasm. Needs a browser with WASM Memory64 (Chrome 133+, Firefox 134+).
|
||||
options.output_type = .EXECUTABLE;
|
||||
options.backend = .LLVM;
|
||||
options.os_target = .WASM;
|
||||
options.cpu_target = .CUSTOM;
|
||||
options.emit_debug_info = .NONE;
|
||||
options.backtrace_on_crash = .OFF;
|
||||
options.llvm_options.target_system_features = "+bulk-memory";
|
||||
options.llvm_options.enable_split_modules = false;
|
||||
options.llvm_options.function_sections = true;
|
||||
STACK_SIZE :: 1024 * 1024;
|
||||
options.additional_linker_arguments = .["--stack-first", "-z", tprint("stack-size=%", STACK_SIZE), tprint("%data/walloc.o", root)];
|
||||
if release set_optimization(*options, .OPTIMIZED);
|
||||
} else {
|
||||
if release {
|
||||
options.backend = .LLVM;
|
||||
set_optimization(*options, .OPTIMIZED);
|
||||
}
|
||||
}
|
||||
|
||||
set_build_options(options, w);
|
||||
if web remap_import(w, "*", "Default_Allocator", "Walloc");
|
||||
|
||||
add_build_file(tprint("%src/main.jai", root), w);
|
||||
|
||||
#if OS == .WINDOWS {
|
||||
// The native client uses libcurl (from the Jai distribution) to talk to the server.
|
||||
if !web for get_build_options().import_path {
|
||||
dll := tprint("%/Curl/windows/lib/libcurl.dll", it);
|
||||
if file_exists(dll) {
|
||||
copy_file(dll, tprint("%1%2libcurl.dll", root, out_dir));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build_server :: (root: string, linux: bool) {
|
||||
out_dir := tprint("%server/bin/", root);
|
||||
make_directory_if_it_does_not_exist(out_dir);
|
||||
w := compiler_create_workspace("TaskForge Server");
|
||||
options := get_build_options(w);
|
||||
copy_commonly_propagated_fields(get_build_options(), *options);
|
||||
options.output_path = out_dir;
|
||||
options.output_executable_name = "taskforge-server";
|
||||
options.backend = .LLVM;
|
||||
set_optimization(*options, .OPTIMIZED);
|
||||
if linux {
|
||||
// There are no Linux system libraries on this machine to link against, so we emit an object file
|
||||
// (with the runtime entry point) and link it on the server, where libc etc. exist.
|
||||
options.os_target = .LINUX;
|
||||
options.cpu_target = .X64;
|
||||
options.output_type = .OBJECT_FILE;
|
||||
options.runtime_support_definitions = .ENTRY_POINT_AND_INIT;
|
||||
}
|
||||
set_build_options(options, w);
|
||||
add_build_file(tprint("%server/server.jai", root), w);
|
||||
#if OS == .WINDOWS {
|
||||
if !linux copy_file(tprint("%server/dev/sqlite3.dll", root), tprint("%sqlite3.dll", out_dir));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Font atlas baking.
|
||||
//
|
||||
// File layout (little endian):
|
||||
// u32 magic 'TFNT', s32 atlas_w, s32 atlas_h, s32 font_count
|
||||
// per font: f32 size, f32 scale, f32 ascent, f32 descent, f32 line_height, s32 first_char, s32 glyph_count
|
||||
// per glyph: s16 x, y, w, h, bearing_x, bearing_y; f32 advance
|
||||
// u8 pixels[atlas_w * atlas_h] (single-channel coverage)
|
||||
//
|
||||
FONT_SIZES :: float.[12, 14, 16, 20, 26]; // Logical sizes used by the UI. Must match Font_Size in src/font.jai.
|
||||
FONT_SCALES :: float.[1, 2]; // Baked for 1x and 2x displays.
|
||||
FIRST_CHAR :: 32;
|
||||
LAST_CHAR :: 255; // ASCII + Latin-1.
|
||||
ATLAS_W :: 1024;
|
||||
ATLAS_MAX_H :: 4096;
|
||||
|
||||
bake_fonts :: (ttf_path: string) -> string, bool {
|
||||
ttf, ok := read_entire_file(ttf_path);
|
||||
if !ok return "", false;
|
||||
|
||||
lib: FT_Library;
|
||||
if FT_Init_FreeType(*lib) return "", false;
|
||||
face: FT_Face;
|
||||
if FT_New_Memory_Face(lib, ttf.data, cast(FT_Long) ttf.count, 0, *face) return "", false;
|
||||
|
||||
pixels := NewArray(ATLAS_W * ATLAS_MAX_H, u8);
|
||||
meta: String_Builder;
|
||||
|
||||
pen_x, pen_y, row_h := 1, 1, 0;
|
||||
font_count := 0;
|
||||
|
||||
for scale: FONT_SCALES {
|
||||
for size: FONT_SIZES {
|
||||
px := cast(u32) (size * scale + 0.5);
|
||||
FT_Set_Pixel_Sizes(face, 0, px);
|
||||
m := face.size.metrics;
|
||||
put(*meta, size);
|
||||
put(*meta, scale);
|
||||
put(*meta, cast(float) m.ascender / 64.0);
|
||||
put(*meta, cast(float) m.descender / 64.0);
|
||||
put(*meta, cast(float) m.height / 64.0);
|
||||
put(*meta, cast(s32) FIRST_CHAR);
|
||||
put(*meta, cast(s32) (LAST_CHAR - FIRST_CHAR + 1));
|
||||
|
||||
for c: FIRST_CHAR..LAST_CHAR {
|
||||
gx, gy, gw, gh, bx, by: s16;
|
||||
advance: float;
|
||||
if FT_Load_Char(face, cast(FT_ULong) c, FT_LOAD_RENDER) == 0 {
|
||||
g := face.glyph;
|
||||
bm := g.bitmap;
|
||||
w := cast(int) bm.width;
|
||||
h := cast(int) bm.rows;
|
||||
if pen_x + w + 1 >= ATLAS_W {
|
||||
pen_x = 1;
|
||||
pen_y += row_h + 1;
|
||||
row_h = 0;
|
||||
}
|
||||
assert(pen_y + h < ATLAS_MAX_H);
|
||||
for y: 0..h-1 {
|
||||
src := bm.buffer + y * bm.pitch;
|
||||
dst := pixels.data + (pen_y + y) * ATLAS_W + pen_x;
|
||||
memcpy(dst, src, w);
|
||||
}
|
||||
gx, gy, gw, gh = xx pen_x, xx pen_y, xx w, xx h;
|
||||
bx, by = xx g.bitmap_left, xx g.bitmap_top;
|
||||
advance = cast(float) g.advance.x / 64.0;
|
||||
pen_x += w + 1;
|
||||
row_h = max(row_h, h);
|
||||
}
|
||||
put(*meta, gx); put(*meta, gy); put(*meta, gw); put(*meta, gh);
|
||||
put(*meta, bx); put(*meta, by); put(*meta, advance);
|
||||
}
|
||||
font_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
atlas_h := pen_y + row_h + 1;
|
||||
atlas_h = (atlas_h + 3) & ~3;
|
||||
|
||||
out: String_Builder;
|
||||
put(*out, cast(u32) 0x544E4654); // 'TFNT'
|
||||
put(*out, cast(s32) ATLAS_W);
|
||||
put(*out, cast(s32) atlas_h);
|
||||
put(*out, cast(s32) font_count);
|
||||
meta_string := builder_to_string(*meta);
|
||||
append(*out, meta_string);
|
||||
append(*out, pixels.data, ATLAS_W * atlas_h);
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(lib);
|
||||
return builder_to_string(*out), true;
|
||||
}
|
||||
|
||||
put :: (b: *String_Builder, value: $T) {
|
||||
v := value;
|
||||
append(b, cast(*u8) *v, size_of(T));
|
||||
}
|
||||
|
||||
#import "Basic";
|
||||
#import "Compiler";
|
||||
#import "File";
|
||||
#import "File_Utilities";
|
||||
#import "freetype";
|
||||
Reference in New Issue
Block a user