From c94e19c434f43295633974afc103a2e80d6ae96b Mon Sep 17 00:00:00 2001 From: JJ Bliss Date: Sat, 25 Apr 2026 19:43:13 -0400 Subject: [PATCH] Got the basics of sprites working --- Makefile | 2 +- src/main.ha | 98 +++++++++++++++++++++--------------------- uxn/uxn.ha | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 166 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index 742740e..2e1cdf1 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ HARE_SOURCES != find . -name '*.ha' build: build/meadow run: build - build/meadow screentest.rom + build/meadow monospace.rom build/meadow: $(HARE_SOURCES) hare build $(LIBS) -o build/meadow src/ diff --git a/src/main.ha b/src/main.ha index 1b98246..ffcabe2 100644 --- a/src/main.ha +++ b/src/main.ha @@ -17,13 +17,13 @@ export fn main() void = { const win = sdl3::CreateWindow( c::nulstr("Meadow\0"), - WIDTH, HEIGHT, 0)!; + WIDTH, HEIGHT, sdl3::WindowFlags::RESIZABLE)!; defer sdl3::DestroyWindow(win); const render = sdl3::CreateRenderer(win, null)!; defer sdl3::DestroyRenderer(render); sdl3::SetRenderDrawColor(render, 0, 0, 128, 255)!; - + sdl3::SetRenderVSync(render,1)!; // const img = image::LoadTexture(render, c::nulstr("./assets/mascot.jpg\0"))!; // defer sdl3::DestroyTexture(img); @@ -58,8 +58,12 @@ export fn main() void = { let path = os::args[1]; let state: *uxn::uxn = uxn::uxn_init(path)!; uxn::uxn_reset(state); + let next_refresh: u64 = 0; + const perf_freq: u64 = sdl3::GetPerformanceFrequency(); + const frame_interval = perf_freq / 60; + const ms_interval = perf_freq / 1000; for (run) { - //UXN stuff + const now: u64 = sdl3::GetPerformanceCounter(); if(state.running){ // fmt::printf("Next Step!\n")!; uxn::uxn_step(state)!; @@ -115,25 +119,31 @@ export fn main() void = { }; }; }; + + if(now >= next_refresh){ + next_refresh = now + frame_interval; + + if(state.screen_vector != 0){ + uxn::uxn_eval(state.screen_vector, state)!; + }; + }; if(state.screen_size_changed){ let dims = uxn::get_window_size(state); // fmt::printfln("Setting window to width: {:x} height: {:x}", dims.width, dims.height)!; if((dims.width != 0) && (dims.height !=0)){ - //TODO, check current dims - // sdl3::DestroyTexture(meadow_texture); let w: int = dims.width: int; let h: int = dims.height: int; fmt::printfln("Setting window to width: {} height: {}", w, h)!; sdl3::SetWindowSize(win,w,h)!; - + meadow_texture = sdl3::CreateTexture(render, sdl3::PixelFormat::XRGB4444, sdl3::TextureAccess::STATIC, w, h)!; - + state.screen_update = true; }; @@ -142,34 +152,23 @@ export fn main() void = { }; if(state.screen_update){ - let pcount = 0; const dims = uxn::get_window_size(state); for(let y: u16 = 0; y <= dims.height: u16; y+=1 ){ for(let x: u16 = 0; x <= dims.width: u16; x+=1 ){ let colshort: u16 = uxn::get_color(x,y,state); - //take 16-bit color and transform to 32-bit number - //XRGB -> XXRXGXBX - // const r = (colshort & 0x0F00): u32 << (3*4); - // const g = (colshort & 0x00F0): u32 << (2*4); - // const b = (colshort & 0x000F): u32 << (1*4); - // const color: u32 = r | g | b; - // const data = pixeldata: *[*]u32; // - // if(colshort !=0x0fff){ - // fmt::printfln("Pixel x: {:x} y: {:x} color: {:x}", x, y, colshort)!; - // }; // fmt::printfln("Pixel x: {} y: {} color: {:x} pcount: {:x}", x, y, colshort, pcount)!; - if(y == 0 && x < 64) colshort = 0; const index: u32 = y: u32 * dims.width: u32 + x: u32; // if(index < 10) fmt::printfln("pcount: {} index: {} x: {} y: {}", pcount, index, x, y)!; pixeldata[index] = colshort; - pcount +=1; }; - // pixeldata[pcount] = 0; - // pcount += 4096; }; const pitch: int = dims.width: int * 2; - fmt::printfln("Pitch is {}", pitch)!; + // fmt::printfln("Pitch is {}", pitch)!; if(pitch > 0) sdl3::UpdateTexture(meadow_texture, null, pixeldata, pitch)!; + sdl3::RenderClear(render)!; + sdl3::RenderTexture(render, meadow_texture, null, null)!; + + sdl3::RenderPresent(render)!; state.screen_update = false; }; @@ -178,38 +177,39 @@ export fn main() void = { for (sdl3::PollEvent(&ev)) { if (ev.event_type == sdl3::EventType::QUIT) { run = false; + }else if(ev.event_type == sdl3::EventType::WINDOW_RESIZED){ + let dims = uxn::get_window_size(state); + let w = ev.window.data1; + let h = ev.window.data2; + if((dims.width != w: u16) || (dims.height != h: u16)){ + // fmt::println("Dimensions not allowed!")!; + //TODO handle this maybe + sdl3::DestroyTexture(meadow_texture); + let old_w: int = dims.width: int; + let old_h: int = dims.height: int; + + fmt::printfln("Setting window to width: {} height: {}", w, h)!; + sdl3::SetWindowSize(win,w,h)!; + + meadow_texture = sdl3::CreateTexture(render, + sdl3::PixelFormat::XRGB4444, + sdl3::TextureAccess::STATIC, + w, + h)!; + + state.screen_update = true; + }; + }; }; - sdl3::RenderClear(render)!; + // sdl3::RenderClear(render)!; + // sdl3::RenderTexture(render, meadow_texture, null, null)!; - sdl3::RenderTexture(render, meadow_texture, null, null)!; + // sdl3::RenderPresent(render)!; + // sdl3::Delay(1000 / 60); - // sdl3::RenderTexture(render, img, null, &sdl3::FRect { - // x = x: f32, - // y = y: f32, - // w = w: f32, - // h = h: f32, - // })!; - - sdl3::RenderPresent(render)!; - sdl3::Delay(1000 / 60); - - // x += xd; - // y += yd; - // if (x + w >= WIDTH) { - // xd = -1; - // }; - // if (x <= 0) { - // xd = 1; - // }; - // if (y + h >= HEIGHT) { - // yd = -1; - // }; - // if (y <= 0) { - // yd = 1; - // }; sdl3::get_error()!; }; diff --git a/uxn/uxn.ha b/uxn/uxn.ha index ac79b2f..c7b4d28 100644 --- a/uxn/uxn.ha +++ b/uxn/uxn.ha @@ -29,6 +29,7 @@ export type uxn = struct { //TODO find a way to use these below export def MAXWIDTH = 4096; export def MAXHEIGHT = 2160; +export def SCREENRATE = 60; //Screen consists of two layers of 2-bit pixels export type uxn_screen = ([MAXWIDTH][MAXHEIGHT]u8, [MAXWIDTH][MAXHEIGHT]u8); @@ -90,7 +91,7 @@ fn emu_deo(port: u8, value: u8, state: *uxn) void = { case 0x2e => draw_pixel(value,state); case 0x2f => - yield; + draw_sprite(value,state); case => if( port == 0x22 || port == 0x23 || @@ -133,13 +134,31 @@ fn draw_pixel(value: u8, state: *uxn) void = { if(!fill){ // fmt::printfln("Pixel at x: {:x} y: {:x} color: {:x}", x, y, color)!; + const auto = state.dev[0x26]; + let count = ((auto & 0xf0) >> 4): i8; + const auto_x = (auto & 0b00000001) != 0; + const auto_y = (auto & 0b00000010) != 0; + const auto_a = (auto & 0b00000100) != 0; if(!layer1){ state.screen.0[x][y] = color; } else { state.screen.1[x][y] = color; }; + if(auto_x) { + const new_x = x + 1; + state.dev[0x28] = (new_x >> 8): u8; + state.dev[0x29] = (new_x): u8; + + }; + if(auto_y) { + const new_y = y + 1; + state.dev[0x2a] = (new_y >> 8): u8; + state.dev[0x2b] = (new_y): u8; + + }; }else{ - fmt::println("Doing Fill")!; + //TODO see if auto still happens with fill + // fmt::println("Doing Fill")!; const dims = get_window_size(state); const startx: u16 = if(flipx) {yield 0; }else{ yield x; }; const starty: u16 = if(flipy) {yield 0; }else{ yield y; }; @@ -162,6 +181,93 @@ fn draw_pixel(value: u8, state: *uxn) void = { state.screen_update = true; }; +fn draw_sprite(value: u8, state: *uxn) void = { + //TODO handle fill + const colors = value & 0b00001111; + const auto = state.dev[0x26]; + let count = ((auto & 0xf0) >> 4): i8; + const auto_x = (auto & 0b00000001) != 0; + const auto_y = (auto & 0b00000010) != 0; + const auto_a = (auto & 0b00000100) != 0; + const bpp2: bool = (value & 0b10000000) != 0; + const layer1: bool = (value & 0b01000000) != 0; + const flipy: bool = (value & 0b00100000) != 0; + const flipx: bool = (value & 0b00010000) != 0; + + for(let i = 0; i <= count; i+=1){ + const addr = short_from_bytes(state.dev[0x2c],state.dev[0x2d]); + const x = short_from_bytes(state.dev[0x28],state.dev[0x29]); + const y = short_from_bytes(state.dev[0x2a],state.dev[0x2b]); + copy_sprite_to_screen(bpp2,colors,x,y,addr,flipx,flipy,layer1,state); + + if(auto_x) { + const new_x = x + 8; + state.dev[0x28] = (new_x >> 8): u8; + state.dev[0x29] = (new_x): u8; + + }; + if(auto_y) { + const new_y = y + 8; + state.dev[0x2a] = (new_y >> 8): u8; + state.dev[0x2b] = (new_y): u8; + + }; + if(auto_a){ + const new_addr = if(bpp2) { yield addr + 16; } else { yield addr + 8; }; + + state.dev[0x2c] = (new_addr >> 8): u8; + state.dev[0x2d] = (new_addr): u8; + }; + }; + state.screen_update = true; +}; + + +fn copy_sprite_to_screen(bpp2: bool, colors: u8, x: u16, y: u16, addr: u16, flipx: bool, flipy: bool, layer1: bool, state: *uxn) void = { + const bytes_per_sprite = if(bpp2) {yield 16;} else {yield 8;}; + let xoff: u16 = 0; + let yoff: u16 = 0; + + + + // fmt::printfln("Drawing Sprite to x: {} y: {}", x, y)!; + for(let i = 0; i < bytes_per_sprite; i+=1){ + let data: u8 = state.ram[addr + i: u16]; + const pixels: []u8 = if(bpp2){ + yield [ + (data & 0b11000000) >> 6, + (data & 0b00110000) >> 4, + (data & 0b00001100) >> 2, + (data & 0b00000011) >> 0 ]; + } else { + yield [ + (data & 0b10000000) >> 7, + (data & 0b01000000) >> 6, + (data & 0b00100000) >> 5, + (data & 0b00010000) >> 4, + (data & 0b00001000) >> 3, + (data & 0b00000100) >> 2, + (data & 0b00000010) >> 1, + (data & 0b00000001) >> 0 ]; + }; + + for(let p .. pixels){ + const color = p; //TODO handle right; + fmt::printfln("Drawing 2bpp: {} Sprite Pixel to x: {} y: {} color: {}", bpp2, x+xoff, y+yoff, p)!; + if(layer1) { state.screen.1[x+xoff][y+yoff] = p; } + else {state.screen.0[x+xoff][y+yoff] = p; }; + xoff += 1; + if(xoff >= 8){ + xoff = 0; + yoff += 1; + }; + + + }; + }; + +}; + export fn get_color(x: u16, y: u16, state: *uxn) u16 = { // check layer 1 let color = state.screen.1[x][y] & 0b00000011; @@ -404,7 +510,7 @@ fn pop_or_get(inst: normal_op, off: u8, state: *uxn) (u8 | u16) = { yield get_stack_val(inst.ret,inst.short, off,state); }; }; -fn uxn_eval(new_pc: u16, state: *uxn) (done | error ) = { +export fn uxn_eval(new_pc: u16, state: *uxn) (done | error ) = { // let a: u16 = 0, b: u16 = 0, c: u16 = 0, x: [2]u16 = [0...], y: [2]u16 = [0...], z: [2]u16 = [0...]; state.pc = new_pc; state.running = true; @@ -833,6 +939,12 @@ export fn uxn_init(path: str) ( *uxn | error ) = { }; io::close(romfile)!; //TODO, isn't there some kind of oversized roms? + //Initialize default size + state.dev[0x22] = 0x02; + state.dev[0x23] = 0x80; + state.dev[0x24] = 0x01; + state.dev[0x25] = 0xe0; + //Initialize default colors TODO look for .theme files state.dev[0x08] = 0xf0; state.dev[0x09] = 0x7f; @@ -844,8 +956,8 @@ export fn uxn_init(path: str) ( *uxn | error ) = { return state; }; export fn uxn_reset(state: *uxn) void = { - state.pc = reset_vector; state.running = true; + uxn_eval(reset_vector,state)!; }; export fn uxnrun() void = { if(len(os::args) < 2){