From 96c2bd5cd550fb224bd9c1f6bbd3aee90f17775f Mon Sep 17 00:00:00 2001 From: koniifer Date: Sun, 13 Oct 2024 23:38:43 +0100 Subject: [PATCH] bitmap image support & better mem opts restructure render lib add peony's render api additions and example add image example fix some dubious bugs i had made in unused code in mem_serve.rs remove tetris stub (now in peony's fork) update hblang --- Cargo.lock | 6 +- kernel/src/holeybytes/ecah.rs | 8 +- .../holeybytes/kernel_services/mem_serve.rs | 65 ++++-- kernel/src/holeybytes/mem.rs | 1 - repbuild/src/main.rs | 7 +- sysdata/libraries/dt_api/src/lib.hb | 2 +- sysdata/libraries/render/src/image.hb | 51 +++++ sysdata/libraries/render/src/lib.hb | 50 ++--- sysdata/libraries/render/src/software.hb | 185 ++++++++---------- sysdata/libraries/render/src/svga.hb | 36 ++-- sysdata/libraries/stn/src/memory.hb | 31 +++ .../render_example/src/examples/image.hb | 31 +++ .../render_example/src/examples/mini.bmp | Bin 0 -> 102454 bytes .../src/examples/tactical_screen.hb | 83 ++++++++ sysdata/programs/render_example/src/main.hb | 2 +- sysdata/programs/tetris/meta.toml | 11 -- sysdata/programs/tetris/src/main.hb | 15 -- 17 files changed, 373 insertions(+), 211 deletions(-) create mode 100644 sysdata/libraries/render/src/image.hb create mode 100644 sysdata/programs/render_example/src/examples/image.hb create mode 100644 sysdata/programs/render_example/src/examples/mini.bmp create mode 100644 sysdata/programs/render_example/src/examples/tactical_screen.hb delete mode 100644 sysdata/programs/tetris/meta.toml delete mode 100644 sysdata/programs/tetris/src/main.hb diff --git a/Cargo.lock b/Cargo.lock index bfd0b3e1d..25cd7bfc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,12 +350,12 @@ checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "hbbytecode" version = "0.1.0" -source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#6d7e726066109e8b08f049bbc4684bba2a2eb88a" +source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#c9b85f9004b7a5d4a4cad68bdf4eb2c1e75d811e" [[package]] name = "hblang" version = "0.1.0" -source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#6d7e726066109e8b08f049bbc4684bba2a2eb88a" +source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#c9b85f9004b7a5d4a4cad68bdf4eb2c1e75d811e" dependencies = [ "hashbrown 0.15.0", "hbbytecode", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "hbvm" version = "0.1.0" -source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#6d7e726066109e8b08f049bbc4684bba2a2eb88a" +source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#c9b85f9004b7a5d4a4cad68bdf4eb2c1e75d811e" dependencies = [ "hbbytecode", ] diff --git a/kernel/src/holeybytes/ecah.rs b/kernel/src/holeybytes/ecah.rs index 8d6769272..0d077d361 100644 --- a/kernel/src/holeybytes/ecah.rs +++ b/kernel/src/holeybytes/ecah.rs @@ -97,7 +97,8 @@ pub fn handler(vm: &mut Vm) { match msg_type { 0 => unsafe { let size = msg_vec[1]; - let addr = u16::from_le_bytes(msg_vec[2..4].try_into().unwrap()); + let addr = + u16::from_le_bytes(msg_vec[2..4].try_into().unwrap_unchecked()); let value = match size { 0 => x86_in::(addr) as u64, 1 => x86_in::(addr) as u64, @@ -109,7 +110,8 @@ pub fn handler(vm: &mut Vm) { }, 1 => unsafe { let size = msg_vec[1]; - let addr = u16::from_le_bytes(msg_vec[2..4].try_into().unwrap()); + let addr = + u16::from_le_bytes(msg_vec[2..4].try_into().unwrap_unchecked()); // info!("Setting address {}", addr); match size { @@ -173,7 +175,7 @@ pub fn handler(vm: &mut Vm) { Ok(msg) => msg, Err(_) => return, }; - if msg.len() > max_length.try_into().unwrap() { + if msg.len() > unsafe { max_length.try_into().unwrap_unchecked() } { info!("{}", max_length); error!("Message is too long to map in."); } else { diff --git a/kernel/src/holeybytes/kernel_services/mem_serve.rs b/kernel/src/holeybytes/kernel_services/mem_serve.rs index 935ca9ba3..e99a1aa4c 100644 --- a/kernel/src/holeybytes/kernel_services/mem_serve.rs +++ b/kernel/src/holeybytes/kernel_services/mem_serve.rs @@ -32,40 +32,37 @@ pub fn memory_msg_handler( let msg_vec = block_read(mem_addr, length); let msg_type = msg_vec[0]; match msg_type { - 0 => { + 0 => unsafe { let page_count = msg_vec[1]; let mptr_raw: [u8; 8] = msg_vec[2..10].try_into().unwrap(); let mptr: u64 = u64::from_le_bytes(mptr_raw); log::debug!("Allocating {} pages @ {:x}", page_count, mptr); - let ptr = unsafe { - alloc(Layout::from_size_align_unchecked( - page_count as usize * 4096, - 4096, - )) - }; + let ptr = alloc(Layout::from_size_align_unchecked( + page_count as usize * 4096, + 4096, + )); vm.registers[1] = hbvm::value::Value(ptr as u64); log::debug!("Kernel ptr: {:x}", ptr as u64); - } + }, - 1 => { + 1 => unsafe { let page_count = msg_vec[1]; let mptr_raw: [u8; 8] = msg_vec[2..10].try_into().unwrap(); let mptr: u64 = u64::from_le_bytes(mptr_raw); log::debug!("Deallocating {} pages @ {:x}", page_count, mptr); - unsafe { - dealloc( - mptr as *mut u8, - Layout::from_size_align_unchecked(page_count as usize * 4096, 4096), - ) - } - } + + dealloc( + mptr as *mut u8, + Layout::from_size_align_unchecked(page_count as usize * 4096, 4096), + ) + }, 2 => { use MemoryQuotaType::*; - let quota_type = match msg_vec[0] { + let quota_type = match msg_vec[1] { 0 => NoQuota, 1 => SoftQuota, 2 => HardQuota, @@ -82,10 +79,42 @@ pub fn memory_msg_handler( ) } 3 => { - let page_count = msg_vec[0]; + let page_count = msg_vec[1]; log::debug!(" {} pages", page_count); } + // memcpy + 4 => unsafe { + let count = u64::from_le_bytes(msg_vec[1..9].try_into().unwrap_unchecked()) as usize; + let src = u64::from_le_bytes(msg_vec[9..17].try_into().unwrap_unchecked()) as *const u8; + let dest = u64::from_le_bytes(msg_vec[17..25].try_into().unwrap_unchecked()) as *mut u8; + src.copy_to(dest, count); + }, + // memset + 5 => unsafe { + let count = u64::from_le_bytes(msg_vec[1..9].try_into().unwrap_unchecked()) as usize; + let size = u64::from_le_bytes(msg_vec[9..17].try_into().unwrap_unchecked()) as usize; + let dest = u64::from_le_bytes(msg_vec[17..25].try_into().unwrap_unchecked()) as *mut u8; + let src = u64::from_le_bytes(msg_vec[25..33].try_into().unwrap_unchecked()) as *mut u8; + let total_size = count * size; + + if total_size > 32 { + core::ptr::copy(src, dest, size); + let pattern = core::slice::from_raw_parts(dest, size); + let mut offset = size; + + while offset < total_size { + let remaining = total_size - offset; + let copy_size = remaining.min(offset); + core::ptr::copy_nonoverlapping(pattern.as_ptr(), dest.add(offset), copy_size); + offset += copy_size; + } + } else { + for i in 0..total_size { + *dest.add(i) = *src.add(i % size); + } + } + }, _ => { log::debug!("Unknown memory service message type: {}", msg_type); } diff --git a/kernel/src/holeybytes/mem.rs b/kernel/src/holeybytes/mem.rs index 79a361491..94d65833f 100644 --- a/kernel/src/holeybytes/mem.rs +++ b/kernel/src/holeybytes/mem.rs @@ -37,7 +37,6 @@ impl hbvm::mem::Memory for Memory { target: *mut u8, count: usize, ) -> Result<(), hbvm::mem::LoadError> { - // if addr.get() % 4096 == 0 {} core::ptr::copy_nonoverlapping(addr.get() as *const u8, target, count); Ok(()) } diff --git a/repbuild/src/main.rs b/repbuild/src/main.rs index 4420c29e2..9963774ea 100644 --- a/repbuild/src/main.rs +++ b/repbuild/src/main.rs @@ -395,12 +395,7 @@ fn run(release: bool, target: Target, do_accel: bool) -> Result<(), Error> { "-parallel", "none", "-monitor", "none", "-machine", accel, - "-cpu", - if accel != "accel=tcg" { - "host" - } else { - "Broadwell-v4" - }, + "-cpu", "max", "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", ]); } diff --git a/sysdata/libraries/dt_api/src/lib.hb b/sysdata/libraries/dt_api/src/lib.hb index 8f2dffe38..9159c8399 100644 --- a/sysdata/libraries/dt_api/src/lib.hb +++ b/sysdata/libraries/dt_api/src/lib.hb @@ -1,6 +1,6 @@ .{string} := @use("../../stn/src/lib.hb") -dt_get := fn(query: ^u8): int { +dt_get := fn($Expr: type, query: ^u8): Expr { length := string.length(query) return @eca(3, 5, query, length) } \ No newline at end of file diff --git a/sysdata/libraries/render/src/image.hb b/sysdata/libraries/render/src/image.hb new file mode 100644 index 000000000..ee43be282 --- /dev/null +++ b/sysdata/libraries/render/src/image.hb @@ -0,0 +1,51 @@ +.{Color} := @use("./lib.hb"); +.{memory, log} := @use("../../stn/src/lib.hb") + +Image := struct { + buf: ^Color, + width: i32, + height: i32, +} + +BitmapFileHeader := packed struct { + img_type: u16, + size: u32, + reserved_1: u16, + reserved_2: u16, + offset: u32, +} + +BitmapInfoHeader := packed struct { + size: u32, + width: i32, + height: i32, + planes: u16, + bits: u16, + compression: u32, + image_size: u32, + x_resolution: i32, + y_resolution: i32, + n_colours: u32, + important_colours: u32, +} + +BitmapColorHeader := packed struct { + red_mask: u32, + green_mask: u32, + blue_mask: u32, + alpha_mask: u32, + color_space_type: u32, + unused: u32, +} + +from_bmp := fn(bmp: ^u8): Image { + file_header := @as(^BitmapFileHeader, @bitcast(bmp)) + if file_header.img_type != 0x4D42 { + log.error("failed to load bmp image: not a bmp image, idiot\0") + return @as(Image, idk) + } + info_header := @as(^BitmapInfoHeader, @bitcast(bmp + @sizeof(BitmapFileHeader))) + bmp += file_header.offset + + return .(@bitcast(bmp), @bitcast(info_header.width), @bitcast(info_header.height)) +} \ No newline at end of file diff --git a/sysdata/libraries/render/src/lib.hb b/sysdata/libraries/render/src/lib.hb index 5c0baed87..556387c68 100644 --- a/sysdata/libraries/render/src/lib.hb +++ b/sysdata/libraries/render/src/lib.hb @@ -1,5 +1,6 @@ svga := @use("svga.hb") software := @use("software.hb") +image := @use("image.hb") // default mode mode := software @@ -8,23 +9,24 @@ init := mode.init doublebuffer := mode.doublebuffer // Colours -Color := mode.Color -white := mode.white -black := mode.black -gray := mode.gray -red := mode.red -green := mode.green -yellow := mode.yellow -blue := mode.blue -magenta := mode.magenta -cyan := mode.cyan -light_gray := mode.light_gray -light_red := mode.light_red -light_green := mode.light_green -light_yellow := mode.light_yellow -light_blue := mode.light_blue -light_magenta := mode.light_magenta -light_cyan := mode.light_cyan +Color := packed struct {b: u8, g: u8, r: u8, a: u8} +Image := image.Image +white := Color.(255, 255, 255, 255) +black := Color.(0, 0, 0, 255) +gray := Color.(127, 127, 127, 255) +red := Color.(0, 0, 205, 255) +green := Color.(0, 205, 0, 255) +yellow := Color.(0, 205, 205, 255) +blue := Color.(205, 0, 0, 255) +magenta := Color.(205, 0, 205, 255) +cyan := Color.(205, 205, 0, 255) +light_gray := Color.(229, 229, 229, 255) +light_red := Color.(0, 0, 255, 255) +light_green := Color.(0, 255, 0, 255) +light_yellow := Color.(0, 255, 255, 255) +light_blue := Color.(255, 0, 0, 255) +light_magenta := Color.(255, 0, 255, 255) +light_cyan := Color.(255, 255, 0, 255) // Drawing put_pixel := mode.put_pixel @@ -32,7 +34,11 @@ put_rect := mode.put_rect put_filled_rect := mode.put_filled_rect put_line := mode.put_line clear := mode.clear -put_img := mode.put_img +put_image := mode.put_image +// thanks peony for these three! +put_trirect := mode.put_trirect +put_vline := mode.put_vline +put_hline := mode.put_hline // Display width := mode.width @@ -41,10 +47,4 @@ dimensions := mode.dimensions set_height := mode.set_height set_width := mode.set_width set_dimensions := mode.set_dimensions -sync := mode.sync - -// Trash Image -Image := struct { - start: ^Color, - end: ^Color, -} \ No newline at end of file +sync := mode.sync \ No newline at end of file diff --git a/sysdata/libraries/render/src/software.hb b/sysdata/libraries/render/src/software.hb index a89057b77..01107dff7 100644 --- a/sysdata/libraries/render/src/software.hb +++ b/sysdata/libraries/render/src/software.hb @@ -1,62 +1,32 @@ .{math, memory} := @use("../../stn/src/lib.hb"); .{dt_get} := @use("../../dt_api/src/lib.hb"); -.{Image} := @use("lib.hb"); +.{Color, Image} := @use("lib.hb"); .{Vec2} := math -Color := struct {b: u8, g: u8, r: u8, a: u8} -white := Color.(255, 255, 255, 255) -black := Color.(0, 0, 0, 255) -gray := Color.(127, 127, 127, 255) -red := Color.(0, 0, 205, 255) -green := Color.(0, 205, 0, 255) -yellow := Color.(0, 205, 205, 255) -blue := Color.(205, 0, 0, 255) -magenta := Color.(205, 0, 205, 255) -cyan := Color.(205, 205, 0, 255) -light_gray := Color.(229, 229, 229, 255) -light_red := Color.(0, 0, 255, 255) -light_green := Color.(0, 255, 0, 255) -light_yellow := Color.(0, 255, 255, 255) -light_blue := Color.(255, 0, 0, 255) -light_magenta := Color.(255, 0, 255, 255) -light_cyan := Color.(255, 255, 0, 255) - -// might not work for some resolutions, but needs to be comptime because... -copy_pixels := 0xC000 >> 2 - ctx := @as(Context, idk) -// some of these are redudant holdovers from fb_driver -// will keep them for future work if necessary Context := struct { fb: ^Color, bb: ^Color, buf: ^Color, width: int, height: int, - partitions: int, pixels: int, - bb_pages: int, double_buffer: bool, } init := fn(): void { - width := dt_get("framebuffer/fb0/width\0") - height := dt_get("framebuffer/fb0/height\0") + width := dt_get(int, "framebuffer/fb0/width\0") + height := dt_get(int, "framebuffer/fb0/height\0") pixels := width * height - bytes := pixels << 2 - partitions := pixels / copy_pixels - pages := 1 + bytes >> 12 - back_buffer := create_back_buffer(pages) + back_buffer := memory.alloc(Color, pixels * @bitcast(@sizeof(Color))) ctx = Context.{ - fb: dt_get("framebuffer/fb0/ptr\0"), + fb: dt_get(^Color, "framebuffer/fb0/ptr\0"), bb: back_buffer, buf: back_buffer, width, height, - partitions, pixels, - bb_pages: pages, double_buffer: true, } return @@ -72,60 +42,12 @@ doublebuffer := fn(enable: bool): void { return } -create_back_buffer := fn(pages: int): ^Color { - if pages <= 0xFF { - return @bitcast(@inline(memory.request_page, pages)) - } - ptr := @inline(memory.request_page, 255) - remaining := pages - 0xFF - loop if remaining <= 0 break else { - if remaining < 0xFF { - memory.request_page(remaining) - } else { - memory.request_page(0xFF) - } - remaining -= 0xFF - } - return @bitcast(ptr) -} - clear := fn(color: Color): void { - cursor := ctx.buf - boundary := cursor + 512 - loop if cursor == boundary break else { - *cursor = color - cursor += 1 - } - boundary += 512 * 7 - loop if cursor == boundary break else { - *@as(^[Color; 512], @bitcast(cursor)) = *@as(^[Color; 512], @bitcast(ctx.buf)) - cursor += 512 - } - boundary += copy_pixels - 4096 - loop if cursor == boundary break else { - *@as(^[Color; 4096], @bitcast(cursor)) = *@as(^[Color; 4096], @bitcast(ctx.buf)) - cursor += 4096 - } - boundary += (ctx.partitions - 1) * copy_pixels - loop if cursor == boundary break else { - *@as(^[Color; copy_pixels], @bitcast(cursor)) = *@as(^[Color; copy_pixels], @bitcast(ctx.buf)) - cursor += @sizeof([u8; copy_pixels]) - } - return + return @inline(memory.set, Color, &color, ctx.buf, @bitcast(ctx.pixels)) } sync := fn(): void { - if ctx.double_buffer { - bb := ctx.buf - fb := ctx.fb - boundary := bb + ctx.pixels - loop if bb == boundary break else { - *@as(^[Color; copy_pixels], @bitcast(fb)) = *@as(^[Color; copy_pixels], @bitcast(bb)) - bb += copy_pixels - fb += copy_pixels - } - } - return + return @inline(memory.copy, Color, ctx.buf, ctx.fb, @bitcast(ctx.pixels)) } width := fn(): int { @@ -146,35 +68,25 @@ put_pixel := fn(pos: Vec2(int), color: Color): void { } put_filled_rect := fn(pos: Vec2(int), tr: Vec2(int), color: Color): void { - x := pos.x y := pos.y - end := pos + tr - loop if x == end.x break else { - loop if y == end.y break else { - *(ctx.buf + @inline(screenidx, x, y)) = color - y += 1 - } - x += 1 - y = pos.y + end_y := y + tr.y + loop if y == end_y break else { + @inline(memory.set, Color, &color, ctx.buf + @inline(screenidx, pos.x, y), @bitcast(tr.x)) + y += 1 } return } put_rect := fn(pos: Vec2(int), tr: Vec2(int), color: Color): void { - x := pos.x y := pos.y - end := pos + tr - loop if y == end.y break else { - *(ctx.buf + @inline(screenidx, x, y)) = color; - *(ctx.buf + @inline(screenidx, x + tr.x, y)) = color + end_y := y + tr.y + loop if y == end_y break else { + *(ctx.buf + @inline(screenidx, pos.x, y)) = color; + *(ctx.buf + @inline(screenidx, pos.x + tr.x, y)) = color y += 1 } - y = pos.y - loop if x == end.x break else { - *(ctx.buf + @inline(screenidx, x, y)) = color; - *(ctx.buf + @inline(screenidx, x, y + tr.y)) = color - x += 1 - } + @inline(memory.set, Color, &color, ctx.buf + @inline(screenidx, pos.x, y), @bitcast(tr.x)) + @inline(memory.set, Color, &color, ctx.buf + @inline(screenidx, pos.x, y - tr.y), @bitcast(tr.x)) return } @@ -259,6 +171,67 @@ set_dimensions := fn(new: Vec2(int)): void { return } -put_img := fn(img: Image, pos: Vec2(int)): void { +put_image := fn(image: Image, pos: Vec2(int)): void { + y := 0 + loop if y == image.height break else { + @inline(memory.copy, Color, image.buf + y * image.width, ctx.buf + @inline(screenidx, pos.x, pos.y + image.height - y), @intcast(image.width)) + y += 1 + } + return +} + +// peony-made +put_trirect := fn(pos: Vec2(int), size: Vec2(int), color0: Color, color1: Color): void { + step := Vec2(int).(1, 1) + if size.x < 0 { + step.x = -1 + } + if size.y < 0 { + step.y = size.y / size.x + } + + start_y := pos.y + target := pos + size + + loop if pos.x == target.x break else { + put_vline(pos.x, pos.y, target.y, color0) + @inline(put_vline, pos.x, pos.y, start_y, color1) + pos += step + } + + return +} + +// peony-made +put_vline := fn(x: int, y0: int, y1: int, color: Color): void { + if y1 < y0 { + tmp := y0 + y0 = y1 + y1 = tmp + } + y := y0 + + loop if y == y1 break else { + *(ctx.buf + @inline(screenidx, x, y)) = color + y += 1 + } + + return +} + +// peony-made +put_hline := fn(y: int, x0: int, x1: int, color: Color): void { + if x1 < x0 { + tmp := x0 + x0 = x1 + x1 = tmp + } + x := x0 + + loop if x == x1 break else { + *(ctx.buf + @inline(screenidx, x, y)) = color + x += 1 + } + return } \ No newline at end of file diff --git a/sysdata/libraries/render/src/svga.hb b/sysdata/libraries/render/src/svga.hb index 0790198ef..033d76269 100644 --- a/sysdata/libraries/render/src/svga.hb +++ b/sysdata/libraries/render/src/svga.hb @@ -1,23 +1,5 @@ -.{Vec2, Image} := @use("lib.hb") -// .{pci, memory, string, log} := @use("../../stn/src/lib.hb"); - -Color := struct {b: u8, g: u8, r: u8, a: u8} -white := Color.(255, 255, 255, 255) -black := Color.(0, 0, 0, 255) -gray := Color.(127, 127, 127, 255) -red := Color.(0, 0, 205, 255) -green := Color.(0, 205, 0, 255) -yellow := Color.(0, 205, 205, 255) -blue := Color.(205, 0, 0, 255) -magenta := Color.(205, 0, 205, 255) -cyan := Color.(205, 205, 0, 255) -light_gray := Color.(229, 229, 229, 255) -light_red := Color.(0, 0, 255, 255) -light_green := Color.(0, 255, 0, 255) -light_yellow := Color.(0, 255, 255, 255) -light_blue := Color.(255, 0, 0, 255) -light_magenta := Color.(255, 0, 255, 255) -light_cyan := Color.(255, 255, 0, 255) +.{Vec2} := @use("../../stn/src/lib.hb").math; +.{Image, Color} := @use("lib.hb") clear := fn(color: Color): void { return @@ -79,6 +61,18 @@ init := fn(): void { return } -put_img := fn(img: Image, pos: Vec2(int)): void { +put_image := fn(img: Image, pos: Vec2(int)): void { + return +} + +put_trirect := fn(pos: Vec2(int), size: Vec2(int), color0: Color, color1: Color): void { + return +} + +put_vline := fn(x: int, y0: int, y1: int, color: Color): void { + return +} + +put_hline := fn(y: int, x0: int, x1: int, color: Color): void { return } \ No newline at end of file diff --git a/sysdata/libraries/stn/src/memory.hb b/sysdata/libraries/stn/src/memory.hb index 4cbdd6d8c..21a0387f4 100644 --- a/sysdata/libraries/stn/src/memory.hb +++ b/sysdata/libraries/stn/src/memory.hb @@ -1,3 +1,24 @@ +PAGE_SIZE := 4096 +MAX_ALLOC := 0xFF + +alloc := fn($Expr: type, bytes: int): ^Expr { + pages := (1 + bytes) / PAGE_SIZE + if pages <= MAX_ALLOC { + return @bitcast(@inline(request_page, pages)) + } + ptr := @inline(request_page, 0xFF) + remaining := pages - MAX_ALLOC + loop if remaining <= 0 break else { + if remaining < MAX_ALLOC { + request_page(remaining) + } else { + request_page(MAX_ALLOC) + } + remaining -= MAX_ALLOC + } + return @bitcast(ptr) +} + request_page := fn(page_count: u8): ^u8 { msg := "\{00}\{01}xxxxxxxx\0" msg_page_count := msg + 1; @@ -36,4 +57,14 @@ outl := fn(addr: u16, value: u32): void { inl := fn(addr: u16): u32 { return @eca(3, 3, &InlMsg.(0, 2, addr), @sizeof(InlMsg)) +} + +CopyMsg := packed struct {a: u8, count: uint, src: uint, dest: ^u8} +copy := fn($Expr: type, src: ^Expr, dest: ^Expr, count: uint): void { + return @eca(3, 2, &CopyMsg.(4, count * @sizeof(Expr), @bitcast(src), @bitcast(dest)), @sizeof(CopyMsg)) +} + +SetMsg := packed struct {a: u8, count: uint, size: uint, dest: ^u8, src: ^u8} +set := fn($Expr: type, src: ^Expr, dest: ^Expr, count: uint): void { + return @eca(3, 2, &SetMsg.(5, count, @sizeof(Expr), @bitcast(dest), @bitcast(src)), @sizeof(SetMsg)) } \ No newline at end of file diff --git a/sysdata/programs/render_example/src/examples/image.hb b/sysdata/programs/render_example/src/examples/image.hb new file mode 100644 index 000000000..6546d7474 --- /dev/null +++ b/sysdata/programs/render_example/src/examples/image.hb @@ -0,0 +1,31 @@ +.{Vec2} := @use("../../../../libraries/stn/src/lib.hb").math; +render := @use("../../../../libraries/render/src/lib.hb") + +/* expected result: + a cute image bounces around the screen */ + +mini_bmp := @embed("./mini.bmp") + +example := fn(): void { + render.init() + mini := render.image.from_bmp(@bitcast(&mini_bmp)) + vel := Vec2(int).(1, 1) + pos := Vec2(int).(100, 100) + width := render.width() + height := render.height() + loop { + render.put_image(mini, pos) + render.sync() + render.clear(render.black) + + if pos.x == 0 | pos.x == width - mini.width { + vel.x = -vel.x + } + if pos.y == 0 | pos.y == height - mini.height { + vel.y = -vel.y + } + + pos += vel + } + return +} \ No newline at end of file diff --git a/sysdata/programs/render_example/src/examples/mini.bmp b/sysdata/programs/render_example/src/examples/mini.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7e0b4ceec3f9c4bdab3a64aa01c75826149183ac GIT binary patch literal 102454 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 X^XDOEuT^AZWLDtJYJIDO@b~`!1F?>E literal 0 HcmV?d00001 diff --git a/sysdata/programs/render_example/src/examples/tactical_screen.hb b/sysdata/programs/render_example/src/examples/tactical_screen.hb new file mode 100644 index 000000000..4f227de27 --- /dev/null +++ b/sysdata/programs/render_example/src/examples/tactical_screen.hb @@ -0,0 +1,83 @@ +render := @use("../../../../libraries/render/src/lib.hb"); +.{math, random} := @use("../../../../libraries/stn/src/lib.hb") +Vec2 := math.Vec2 + +/* expected result: + a grid of green lines scrolling from the left top corner to the right bottom one + with a "target" randomly apperaing in one of them and a "seeker" "catching" it*/ + +example := fn(): void { + render.init() + + width := render.width() + height := render.height() + cell_size := 0 + range := Vec2(int).(0, 0) + if width > height { + cell_size = width / 40 + range = .(39, height / cell_size - 1) + } else { + cell_size = height / 40 + range = .(width / cell_size - 1, 39) + } + width -= 1 + height -= 1 + + scroll := 0 + target := Vec2(int).(random.range(int, 0, range.x), random.range(int, 0, range.y)) + + halfcell := cell_size / 2 + octcell := cell_size / 8 + sevenoctcell := cell_size - octcell + + seeker := Vec2(int).(random.range(int, 0, range.x), random.range(int, 0, range.y)) + + loop { + render.clear(render.black) + + target_pixel_coord := target * .(cell_size, cell_size) + .(scroll, scroll) + render.put_trirect(target_pixel_coord, .(cell_size, cell_size), render.red, render.light_red) + + render.put_hline(target_pixel_coord.y + halfcell, target_pixel_coord.x - octcell, target_pixel_coord.x - sevenoctcell, render.light_red) + render.put_hline(target_pixel_coord.y + halfcell, target_pixel_coord.x + cell_size + octcell, target_pixel_coord.x + cell_size + sevenoctcell, render.light_red) + render.put_vline(target_pixel_coord.x + halfcell, target_pixel_coord.y - octcell, target_pixel_coord.y - sevenoctcell, render.light_red) + render.put_vline(target_pixel_coord.x + halfcell, target_pixel_coord.y + cell_size + octcell, target_pixel_coord.y + cell_size + sevenoctcell, render.light_red) + + x := scroll + loop if x > width break else { + render.put_vline(x, 0, height, .(0, 127, 0, 127)) + x += cell_size + } + + y := scroll + loop if y > height break else { + render.put_hline(y, 0, width, .(0, 127, 0, 127)) + y += cell_size + } + + render.put_hline(seeker.y * cell_size + halfcell + scroll, 0, width, render.light_green) + render.put_vline(seeker.x * cell_size + halfcell + scroll, 0, height, render.light_green) + + render.sync() + + if seeker.x < target.x { + seeker.x += 1 + } else if seeker.x > target.x { + seeker.x -= 1 + } else if seeker.y < target.y { + seeker.y += 1 + } else if seeker.y > target.y { + seeker.y -= 1 + } else { + target = .(random.range(int, 0, range.x), random.range(int, 0, range.y)) + } + + scroll += 1 + if scroll > cell_size { + scroll = 0 + target += .(1, 1) + seeker += .(1, 1) + } + } + return +} \ No newline at end of file diff --git a/sysdata/programs/render_example/src/main.hb b/sysdata/programs/render_example/src/main.hb index 35b2706e4..1ce2d4e68 100644 --- a/sysdata/programs/render_example/src/main.hb +++ b/sysdata/programs/render_example/src/main.hb @@ -1,4 +1,4 @@ -.{example} := @use("./examples/amogus.hb") +.{example} := @use("./examples/image.hb") main := fn(): void { @inline(example) diff --git a/sysdata/programs/tetris/meta.toml b/sysdata/programs/tetris/meta.toml deleted file mode 100644 index d2a5f974c..000000000 --- a/sysdata/programs/tetris/meta.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tetris" -authors = ["peony"] - -[dependants.libraries] - -[dependants.binaries] -hblang.version = "1.0.0" - -[build] -command = "hblang src/main.hb" diff --git a/sysdata/programs/tetris/src/main.hb b/sysdata/programs/tetris/src/main.hb deleted file mode 100644 index e61324b32..000000000 --- a/sysdata/programs/tetris/src/main.hb +++ /dev/null @@ -1,15 +0,0 @@ -.{memory, log, string, buffer} := @use("../../../libraries/stn/src/lib.hb") - -main := fn(): void { - storage := @as(u8, 0) - output_buffer := memory.request_page(1) - input_buffer := buffer.search("XKeyboard\0") - loop { - buffer.recv(input_buffer, &storage, 1) - if storage != 0 { - log.info(string.display_int(storage, output_buffer)) - storage = 0 - } - } - return -} \ No newline at end of file