diff --git a/demo/cart.fnl b/demo/cart.fnl index 1c3d431..235cffe 100644 --- a/demo/cart.fnl +++ b/demo/cart.fnl @@ -1,3 +1,4 @@ {:title "pxl8 demo" + :pixel-mode "indexed" :resolution "640x360" :window-size [1280 720]} diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index 3b2afa6..20c1240 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -18,10 +18,6 @@ (local SIM_FLAG_GROUNDED 4) -(local door-spawn-x 860) -(local door-spawn-z 416) -(local door-spawn-yaw 1.5708) - (local sim-cfg (pxl8.sim_config {:move_speed 150 :gravity 600 :jump_velocity 180 @@ -45,14 +41,13 @@ (var cam-z 416) (var camera nil) (var ceiling-tex nil) -(var current-bsp-id 1) (var floor-tex nil) (var land-squash 0) (var last-dt 0.016) (var light-time 0) (var glows nil) (var lights nil) -(var materials-for-chunk 0) +(var bsp-materials-setup false) (var network nil) (var portal-cooldown 0) (var real-time 0) @@ -68,11 +63,6 @@ (local STONE_WALL_START 2) (local WOOD_COLOR 88) -(local ASHLAR_COLOR 64) -(local ASHLAR_MOSS 68) -(local STONE_FLOOR_COLOR 72) -(local STONE_TRIM_COLOR 72) - (fn preload [] (when (not network) (set network (net.get)) @@ -93,20 +83,6 @@ (let [chunk (world:active_chunk)] (and chunk (chunk:ready))))) -(fn setup-textures [bsp-id] - (if (= bsp-id 2) - (do - (set floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR)) - (set wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS)) - (set trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR)) - (set ceiling-tex (textures.plaster-wall 66662 PLASTER_COLOR))) - (do - (set floor-tex (textures.wood-planks 44444 WOOD_COLOR)) - (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)) - (set trim-tex (textures.wood-trim 77777 WOOD_COLOR)) - (set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR)))) - (set current-bsp-id bsp-id)) - (fn init [] (pxl8.set_relative_mouse_mode true) (pxl8.load_palette "res/palettes/palette.ase") @@ -137,28 +113,27 @@ (set smooth-cam-x cam-x) (set smooth-cam-z cam-z)) - (setup-textures 1)) + (when (not ceiling-tex) + (set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR))) + (when (not floor-tex) + (set floor-tex (textures.wood-planks 44444 WOOD_COLOR))) + (when (not trim-tex) + (set trim-tex (textures.wood-trim 77777 WOOD_COLOR))) + (when (not wall-tex) + (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)))) (fn setup-materials [] - (when (not world) (lua "return")) - (when (not network) (lua "return")) - (let [net-chunk (network:chunk_id)] - (when (or (= net-chunk 0) (= materials-for-chunk net-chunk)) (lua "return")) - (when (not= current-bsp-id net-chunk) - (setup-textures net-chunk)) - (when (not floor-tex) (lua "return")) - (when (not wall-tex) (lua "return")) + (when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex) (let [chunk (world:active_chunk)] - (when (not chunk) (lua "return")) - (when (not (chunk:ready)) (lua "return")) - (let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true}) - trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true}) - wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})] - (world:set_bsp_material 0 floor-mat) - (world:set_bsp_material 1 wall-mat) - (world:set_bsp_material 3 trim-mat) - (entities.setup-lighting (chunk:bsp) 2) - (set materials-for-chunk net-chunk))))) + (when (and chunk (chunk:ready)) + (let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true}) + trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true}) + wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})] + (world:set_bsp_material 0 floor-mat) + (world:set_bsp_material 1 wall-mat) + (world:set_bsp_material 3 trim-mat) + (entities.setup-lighting (chunk:bsp) 2) + (set bsp-materials-setup true)))))) (fn sample-input [] (when (pxl8.key_pressed "`") @@ -199,32 +174,26 @@ (if (= current-id 1) (do (pxl8.info "Door: BSP 1 -> BSP 2") - (network:enter_scene 1 2 door-spawn-x 0 door-spawn-z) - (world:init_local_player door-spawn-x 0 door-spawn-z) - (world:set_look door-spawn-yaw 0) - (set cam-x door-spawn-x) + (network:enter_scene 1 2 416 0 416) + (world:init_local_player 416 0 416) + (set cam-x 416) (set cam-y 0) - (set cam-z door-spawn-z) - (set cam-yaw door-spawn-yaw) - (set cam-pitch 0) - (set smooth-cam-x door-spawn-x) - (set smooth-cam-z door-spawn-z) - (setup-textures 2) + (set cam-z 416) + (set smooth-cam-x 416) + (set smooth-cam-z 416) + (set bsp-materials-setup false) (set portal-cooldown 2.0)) (= current-id 2) (do (pxl8.info "Door: BSP 2 -> BSP 1") - (network:enter_scene 1 1 door-spawn-x 0 door-spawn-z) - (world:init_local_player door-spawn-x 0 door-spawn-z) - (world:set_look door-spawn-yaw 0) - (set cam-x door-spawn-x) + (network:enter_scene 1 1 416 0 416) + (world:init_local_player 416 0 416) + (set cam-x 416) (set cam-y 0) - (set cam-z door-spawn-z) - (set cam-yaw door-spawn-yaw) - (set cam-pitch 0) - (set smooth-cam-x door-spawn-x) - (set smooth-cam-z door-spawn-z) - (setup-textures 1) + (set cam-z 416) + (set smooth-cam-x 416) + (set smooth-cam-z 416) + (set bsp-materials-setup false) (set portal-cooldown 2.0))))))) (when world @@ -298,8 +267,7 @@ r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase))) light-radius (* 150 (+ 0.95 r1 r2))] (lights:clear) - (when (= current-bsp-id 1) - (lights:add light-x light-y light-z 2 light-intensity light-radius)) + (lights:add light-x light-y light-z 2 light-intensity light-radius) (pxl8.push_target) (pxl8.begin_frame_3d camera lights { @@ -317,7 +285,7 @@ (pxl8.set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) - (when (and chunk (= current-bsp-id 1)) + (when chunk (entities.render-fireball light-x light-y light-z (menu.is-wireframe))) (entities.render-door (menu.is-wireframe) 0) diff --git a/pxl8d/src/main.rs b/pxl8d/src/main.rs index bbcb4fb..e67ec8a 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -6,7 +6,6 @@ extern crate alloc; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; -use pxl8d::procgen::LayoutMode; const TICK_RATE: u64 = 30; const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; @@ -127,8 +126,9 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { width: 20, height: 20, seed: 12345u32.wrapping_add(2), - layout: LayoutMode::Courtyard, - ..ProcgenParams::default() + min_room_size: 14, + max_room_size: 16, + num_rooms: 1, }), _ => None, }; @@ -174,7 +174,6 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { if let Some(pid) = player_id { if let Some(_player) = sim.get_player_position(pid) { - let mut burst = false; while let Some(chunk_id) = client_chunks.next_pending() { match chunk_id { ChunkId::Bsp(id) => { @@ -183,15 +182,13 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { let msgs = bsp_to_messages(bsp, id, chunk.version()); client_chunks.queue_messages(msgs); client_chunks.mark_sent(chunk_id, chunk.version()); - burst = true; } } } } } - let send_limit = if burst { 256 } else { 8 }; - for _ in 0..send_limit { + for _ in 0..8 { if let Some(msg) = client_chunks.next_message() { transport.send_chunk(&msg, sequence); sequence = sequence.wrapping_add(1); diff --git a/pxl8d/src/procgen.rs b/pxl8d/src/procgen.rs index 6be0cb9..5818198 100644 --- a/pxl8d/src/procgen.rs +++ b/pxl8d/src/procgen.rs @@ -19,12 +19,6 @@ pub struct LightSource { pub radius: f32, } -#[derive(Clone, Copy, PartialEq)] -pub enum LayoutMode { - Dungeon, - Courtyard, -} - #[derive(Clone, Copy)] pub struct ProcgenParams { pub width: i32, @@ -33,7 +27,6 @@ pub struct ProcgenParams { pub min_room_size: i32, pub max_room_size: i32, pub num_rooms: i32, - pub layout: LayoutMode, } impl Default for ProcgenParams { @@ -45,7 +38,6 @@ impl Default for ProcgenParams { min_room_size: 3, max_room_size: 6, num_rooms: 8, - layout: LayoutMode::Dungeon, } } } @@ -169,15 +161,7 @@ fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: let plane_idx = ctx.plane_offset; ctx.plane_offset += 1; - let split_x = if x1 - x0 <= 1 { - false - } else if y1 - y0 <= 1 { - true - } else { - depth % 2 == 0 - }; - - if split_x { + if depth % 2 == 0 { let mid_x = (x0 + x1) / 2; let split_pos = mid_x as f32 * CELL_SIZE; @@ -487,16 +471,6 @@ fn compute_vertex_ao(bsp: &BspBuilder, pos: Vec3, normal: Vec3) -> f32 { continue; } - let cx = offset_pos.x.max(face.aabb_min.x).min(face.aabb_max.x); - let cy = offset_pos.y.max(face.aabb_min.y).min(face.aabb_max.y); - let cz = offset_pos.z.max(face.aabb_min.z).min(face.aabb_max.z); - let dx = offset_pos.x - cx; - let dy = offset_pos.y - cy; - let dz = offset_pos.z - cz; - if dx * dx + dy * dy + dz * dz > AO_RAY_LENGTH * AO_RAY_LENGTH { - continue; - } - let mut verts = [Vec3::new(0.0, 0.0, 0.0); 4]; let mut num_verts = 0usize; @@ -1100,144 +1074,6 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { bsp.into() } -pub fn generate_courtyard(params: &ProcgenParams) -> Bsp { - let mut rng = Rng::new(params.seed); - let mut grid = RoomGrid::new(params.width, params.height); - grid.fill(1); - - let mut rooms: Vec = Vec::new(); - - let cx = params.width / 2; - let cy = params.height / 2; - let court_half = 4; - let court_x = cx - court_half; - let court_y = cy - court_half; - let court_w = court_half * 2; - let court_h = court_half * 2; - - for ry in court_y..(court_y + court_h) { - for rx in court_x..(court_x + court_w) { - grid.set(rx, ry, 0); - } - } - rooms.push(Bounds { x: court_x, y: court_y, w: court_w, h: court_h }); - - let wing_dirs: [(i32, i32); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)]; - - for &(dx, dy) in &wing_dirs { - let room_w = 3 + (rng.next() % 3) as i32; - let room_h = 3 + (rng.next() % 3) as i32; - - let (rx, ry) = if dx != 0 { - let rx = if dx > 0 { court_x + court_w + 2 } else { court_x - room_w - 2 }; - let ry = cy - room_h / 2; - (rx, ry) - } else { - let rx = cx - room_w / 2; - let ry = if dy > 0 { court_y + court_h + 2 } else { court_y - room_h - 2 }; - (rx, ry) - }; - - let rx = rx.max(1).min(params.width - room_w - 1); - let ry = ry.max(1).min(params.height - room_h - 1); - - for y in ry..(ry + room_h) { - for x in rx..(rx + room_w) { - grid.set(x, y, 0); - } - } - - let room_cx = rx + room_w / 2; - let room_cy = ry + room_h / 2; - if dx != 0 { - carve_corridor_h(&mut grid, cx, room_cx, room_cy); - } else { - carve_corridor_v(&mut grid, cy, room_cy, room_cx); - } - - let primary = Bounds { x: rx, y: ry, w: room_w, h: room_h }; - rooms.push(primary); - - let extra_count = 1 + (rng.next() % 2) as i32; - for _ in 0..extra_count { - let ew = 2 + (rng.next() % 3) as i32; - let eh = 2 + (rng.next() % 3) as i32; - - let (ex, ey) = if dx != 0 { - let ex = if dx > 0 { rx + room_w + 1 } else { rx - ew - 1 }; - let ey = ry + (rng.next() % room_h.max(1) as u32) as i32 - eh / 2; - (ex, ey) - } else { - let ex = rx + (rng.next() % room_w.max(1) as u32) as i32 - ew / 2; - let ey = if dy > 0 { ry + room_h + 1 } else { ry - eh - 1 }; - (ex, ey) - }; - - let ex = ex.max(1).min(params.width - ew - 1); - let ey = ey.max(1).min(params.height - eh - 1); - - for y in ey..(ey + eh) { - for x in ex..(ex + ew) { - grid.set(x, y, 0); - } - } - - let ecx = ex + ew / 2; - let ecy = ey + eh / 2; - if rng.next() % 2 == 0 { - carve_corridor_h(&mut grid, room_cx, ecx, room_cy); - carve_corridor_v(&mut grid, room_cy, ecy, ecx); - } else { - carve_corridor_v(&mut grid, room_cy, ecy, room_cx); - carve_corridor_h(&mut grid, room_cx, ecx, ecy); - } - - rooms.push(Bounds { x: ex, y: ey, w: ew, h: eh }); - } - } - - let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &grid); - - let light_height = 80.0; - let fireball_pos = Vec3::new(384.0, light_height, 324.0); - let fireball_exclusion_radius = 150.0; - - let lights: Vec = rooms.iter().filter_map(|room| { - let room_cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - let room_cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; - - let ddx = room_cx - fireball_pos.x; - let ddz = room_cz - fireball_pos.z; - if ddx * ddx + ddz * ddz < fireball_exclusion_radius * fireball_exclusion_radius { - return None; - } - - let intensity = if room.w >= 6 && room.h >= 6 { 2.0 } else { 1.5 }; - let radius = if room.w >= 6 && room.h >= 6 { 250.0 } else { 160.0 }; - - Some(LightSource { - position: Vec3::new(room_cx, light_height, room_cz), - intensity, - radius, - }) - }).collect(); - - let mut lights = lights; - lights.push(LightSource { - position: Vec3::new(860.0, light_height, 416.0), - intensity: 1.2, - radius: 120.0, - }); - - compute_bsp_vertex_lighting(&mut bsp, &lights); - - bsp.into() -} - pub fn generate(params: &ProcgenParams) -> Bsp { - match params.layout { - LayoutMode::Dungeon => generate_rooms(params), - LayoutMode::Courtyard => generate_courtyard(params), - } + generate_rooms(params) } diff --git a/pxl8d/src/world.rs b/pxl8d/src/world.rs index 13bb8c0..087ce7c 100644 --- a/pxl8d/src/world.rs +++ b/pxl8d/src/world.rs @@ -4,7 +4,7 @@ use alloc::collections::BTreeMap; use crate::chunk::{Chunk, ChunkId}; use crate::math::Vec3; -use crate::procgen::{ProcgenParams, generate}; +use crate::procgen::{ProcgenParams, generate_rooms}; pub struct World { active: Option, @@ -69,7 +69,7 @@ impl World { ..Default::default() }; let p = params.unwrap_or(&default_params); - let bsp = generate(p); + let bsp = generate_rooms(p); self.chunks.insert(chunk_id, Chunk::Bsp { id, bsp, version: 1 }); } self.chunks.get(&chunk_id).unwrap() diff --git a/src/asset/pxl8_cart.c b/src/asset/pxl8_cart.c index 45be3fc..9f4131e 100644 --- a/src/asset/pxl8_cart.c +++ b/src/asset/pxl8_cart.c @@ -49,6 +49,7 @@ struct pxl8_cart { char* title; pxl8_resolution resolution; pxl8_size window_size; + pxl8_pixel_mode pixel_mode; bool is_folder; bool is_mounted; }; @@ -155,6 +156,7 @@ pxl8_cart* pxl8_cart_create(void) { if (cart) { cart->resolution = PXL8_RESOLUTION_640x360; cart->window_size = (pxl8_size){1280, 720}; + cart->pixel_mode = PXL8_PIXEL_INDEXED; } return cart; } @@ -362,6 +364,14 @@ void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) { if (cart) cart->window_size = size; } +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) { + return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED; +} + +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) { + if (cart) cart->pixel_mode = mode; +} + bool pxl8_cart_is_packed(const pxl8_cart* cart) { return cart && !cart->is_folder; } diff --git a/src/asset/pxl8_cart.h b/src/asset/pxl8_cart.h index e81b406..9384192 100644 --- a/src/asset/pxl8_cart.h +++ b/src/asset/pxl8_cart.h @@ -25,9 +25,11 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart); const char* pxl8_cart_get_title(const pxl8_cart* cart); pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart); pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart); +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart); void pxl8_cart_set_title(pxl8_cart* cart, const char* title); void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution); void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size); +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode); bool pxl8_cart_is_packed(const pxl8_cart* cart); bool pxl8_cart_has_embedded(const char* exe_path); diff --git a/src/core/pxl8.c b/src/core/pxl8.c index 3ae384c..5d9d794 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -239,6 +239,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { const char* window_title = pxl8_cart_get_title(sys->cart); if (!window_title) window_title = "pxl8"; pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart); + pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart); pxl8_size window_size = pxl8_cart_get_window_size(sys->cart); pxl8_size render_size = pxl8_get_resolution_dimensions(resolution); @@ -248,7 +249,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } - game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, resolution); + game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution); if (!game->gfx) { pxl8_error("failed to create graphics context"); return PXL8_ERROR_INITIALIZATION_FAILED; diff --git a/src/core/pxl8_types.h b/src/core/pxl8_types.h index 83c4122..761afea 100644 --- a/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -31,6 +31,12 @@ typedef __int128_t i128; typedef __uint128_t u128; #endif +typedef enum pxl8_pixel_mode { + PXL8_PIXEL_INDEXED = 1, + PXL8_PIXEL_HICOLOR = 2, + PXL8_PIXEL_RGBA = 4, +} pxl8_pixel_mode; + typedef enum pxl8_cursor { PXL8_CURSOR_ARROW, PXL8_CURSOR_HAND diff --git a/src/gfx/pxl8_atlas.c b/src/gfx/pxl8_atlas.c index 3811699..910dba6 100644 --- a/src/gfx/pxl8_atlas.c +++ b/src/gfx/pxl8_atlas.c @@ -1,8 +1,11 @@ #include "pxl8_atlas.h" #include +#include +#include #include +#include "pxl8_color.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -141,14 +144,15 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) { } } -pxl8_atlas* pxl8_atlas_create(u32 width, u32 height) { +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) { pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas)); if (!atlas) return NULL; atlas->height = height; atlas->width = width; - atlas->pixels = (u8*)pxl8_calloc(width * height, 1); + i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); + atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel); if (!atlas->pixels) { pxl8_free(atlas); return NULL; @@ -222,13 +226,14 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) { atlas->dirty = true; } -bool pxl8_atlas_expand(pxl8_atlas* atlas) { +bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) { if (!atlas || atlas->width >= 4096) return false; + i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); u32 new_size = atlas->width * 2; u32 old_width = atlas->width; - u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, 1); + u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel); if (!new_pixels) return false; pxl8_skyline new_skyline; @@ -263,7 +268,11 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas) { for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) { u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x); u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x); - new_pixels[dst_idx] = atlas->pixels[src_idx]; + if (bytes_per_pixel == 2) { + ((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx]; + } else { + new_pixels[dst_idx] = atlas->pixels[src_idx]; + } } } @@ -295,14 +304,15 @@ u32 pxl8_atlas_add_texture( pxl8_atlas* atlas, const u8* pixels, u32 w, - u32 h + u32 h, + pxl8_pixel_mode pixel_mode ) { if (!atlas || !pixels) return UINT32_MAX; pxl8_skyline_fit fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); if (!fit.found) { - if (!pxl8_atlas_expand(atlas)) { + if (!pxl8_atlas_expand(atlas, pixel_mode)) { return UINT32_MAX; } @@ -337,11 +347,17 @@ u32 pxl8_atlas_add_texture( entry->h = h; entry->log2_w = pxl8_log2(w); + i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { u32 src_idx = y * w + x; u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x); - atlas->pixels[dst_idx] = pixels[src_idx]; + + if (bytes_per_pixel == 2) { + ((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx]; + } else { + atlas->pixels[dst_idx] = pixels[src_idx]; + } } } diff --git a/src/gfx/pxl8_atlas.h b/src/gfx/pxl8_atlas.h index 46d46e2..94e0a95 100644 --- a/src/gfx/pxl8_atlas.h +++ b/src/gfx/pxl8_atlas.h @@ -30,11 +30,11 @@ static inline u8 pxl8_log2(u32 v) { extern "C" { #endif -u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h); +u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode); void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count); -pxl8_atlas* pxl8_atlas_create(u32 width, u32 height); +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode); void pxl8_atlas_destroy(pxl8_atlas* atlas); -bool pxl8_atlas_expand(pxl8_atlas* atlas); +bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode); const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); diff --git a/src/gfx/pxl8_blit.c b/src/gfx/pxl8_blit.c index 8fa3192..7409066 100644 --- a/src/gfx/pxl8_blit.c +++ b/src/gfx/pxl8_blit.c @@ -1,7 +1,34 @@ #include "pxl8_blit.h" -void pxl8_blit(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, - i32 x, i32 y, u32 w, u32 h) { +void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width, + i32 x, i32 y, u32 w, u32 h) { + u16* dest_base = fb + y * fb_width + x; + const u16* src_base = sprite; + + for (u32 row = 0; row < h; row++) { + u16* dest_row = dest_base + row * fb_width; + const u16* src_row = src_base + row * atlas_width; + + u32 col = 0; + u32 count2 = w / 2; + for (u32 i = 0; i < count2; i++) { + u32 pixels = ((const u32*)src_row)[i]; + if (pixels == 0) { + col += 2; + continue; + } + dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]); + dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]); + col += 2; + } + if (w & 1) { + dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]); + } + } +} + +void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, + i32 x, i32 y, u32 w, u32 h) { u8* dest_base = fb + y * fb_width + x; const u8* src_base = sprite; @@ -17,14 +44,14 @@ void pxl8_blit(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, col += 4; continue; } - dest_row[col] = pxl8_blit_mask((u8)(pixels), dest_row[col]); - dest_row[col + 1] = pxl8_blit_mask((u8)(pixels >> 8), dest_row[col + 1]); - dest_row[col + 2] = pxl8_blit_mask((u8)(pixels >> 16), dest_row[col + 2]); - dest_row[col + 3] = pxl8_blit_mask((u8)(pixels >> 24), dest_row[col + 3]); + dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]); + dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]); + dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]); + dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]); col += 4; } for (; col < w; col++) { - dest_row[col] = pxl8_blit_mask(src_row[col], dest_row[col]); + dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]); } } } diff --git a/src/gfx/pxl8_blit.h b/src/gfx/pxl8_blit.h index 63a65c6..0ab5bd8 100644 --- a/src/gfx/pxl8_blit.h +++ b/src/gfx/pxl8_blit.h @@ -6,12 +6,22 @@ extern "C" { #endif -static inline u8 pxl8_blit_mask(u8 src, u8 dst) { +static inline u8 pxl8_blend_indexed(u8 src, u8 dst) { u8 m = (u8)(-(src != 0)); return (src & m) | (dst & ~m); } -void pxl8_blit( +static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) { + u16 m = (u16)(-(src != 0)); + return (src & m) | (dst & ~m); +} + +void pxl8_blit_hicolor( + u16* fb, u32 fb_width, + const u16* sprite, u32 atlas_width, + i32 x, i32 y, u32 w, u32 h +); +void pxl8_blit_indexed( u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, i32 x, i32 y, u32 w, u32 h diff --git a/src/gfx/pxl8_color.h b/src/gfx/pxl8_color.h index 8915454..f159724 100644 --- a/src/gfx/pxl8_color.h +++ b/src/gfx/pxl8_color.h @@ -2,6 +2,10 @@ #include "pxl8_types.h" +static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) { + return (i32)mode; +} + static inline u32 pxl8_color_from_rgba(u32 rgba) { u8 r = (rgba >> 24) & 0xFF; u8 g = (rgba >> 16) & 0xFF; @@ -72,10 +76,30 @@ static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) { *b = (bi << 6) | (bi << 4) | (bi << 2) | bi; } +static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) { + return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); +} + +static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) { + *r = (color >> 11) << 3; + *g = ((color >> 5) & 0x3F) << 2; + *b = (color & 0x1F) << 3; +} + +static inline u32 pxl8_rgb565_to_rgba32(u16 color) { + u8 r, g, b; + pxl8_rgb565_unpack(color, &r, &g, &b); + return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000; +} + static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) { return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); } +static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) { + return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF); +} + static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { *r = color & 0xFF; *g = (color >> 8) & 0xFF; diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index 9b331e1..de4ad2c 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -6,6 +6,7 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" #include "pxl8_blit.h" +#include "pxl8_color.h" #include "pxl8_colormap.h" #include "pxl8_font.h" #include "pxl8_glows.h" @@ -71,6 +72,7 @@ struct pxl8_gfx { pxl8_palette_cube* palette_cube; pxl8_gfx_pass frame_pass; pxl8_frame_resources frame_res; + pxl8_pixel_mode pixel_mode; void* platform_data; pxl8_sprite_cache_entry* sprite_cache; u32 sprite_cache_capacity; @@ -98,11 +100,20 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { return bounds; } -u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx) { - if (!gfx) return NULL; +pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { + return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED; +} + +u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { + if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); } +u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { + if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; + return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); +} + i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_height : 0; } @@ -114,8 +125,9 @@ u32* pxl8_gfx_get_output(pxl8_gfx* gfx) { void pxl8_gfx_resolve(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized) return; + if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return; - const u32* pal = pxl8_palette_colors(gfx->palette); + const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; if (!pal) { pxl8_error("resolve: no palette!"); return; @@ -168,8 +180,10 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { if (!gfx || !gfx->palette || !filepath) return -1; pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath); if (result != PXL8_OK) return (i32)result; - if (gfx->colormap) { - pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette)); + if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { + if (gfx->colormap) { + pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette)); + } } return 0; } @@ -177,6 +191,7 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { pxl8_gfx* pxl8_gfx_create( const pxl8_hal* hal, void* platform_data, + pxl8_pixel_mode mode, pxl8_resolution resolution ) { pxl8_shader_registry_init(); @@ -190,6 +205,7 @@ pxl8_gfx* pxl8_gfx_create( gfx->hal = hal; gfx->platform_data = platform_data; + gfx->pixel_mode = mode; pxl8_size size = pxl8_get_resolution_dimensions(resolution); gfx->framebuffer_width = size.w; gfx->framebuffer_height = size.h; @@ -200,7 +216,9 @@ pxl8_gfx* pxl8_gfx_create( return NULL; } - gfx->palette = pxl8_palette_create(); + if (mode != PXL8_PIXEL_HICOLOR) { + gfx->palette = pxl8_palette_create(); + } gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height); if (!gfx->renderer) { @@ -240,7 +258,9 @@ pxl8_gfx* pxl8_gfx_create( return NULL; } - gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap)); + if (mode != PXL8_PIXEL_HICOLOR) { + gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap)); + } gfx->target_stack[0] = (pxl8_target_entry){ .color = gfx->color_target, @@ -293,7 +313,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) { static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) { if (gfx->atlas) return PXL8_OK; - gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE); + gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode); return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY; } @@ -315,7 +335,7 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, pxl8_result result = pxl8_gfx_ensure_atlas(gfx); if (result != PXL8_OK) return result; - u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height); + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode); if (texture_id == UINT32_MAX) { pxl8_error("Texture doesn't fit in atlas"); return PXL8_ERROR_INVALID_SIZE; @@ -361,7 +381,8 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { gfx->atlas, ase_file.frames[0].pixels, ase_file.header.width, - ase_file.header.height + ase_file.header.height, + gfx->pixel_mode ); pxl8_ase_destroy(&ase_file); @@ -405,14 +426,28 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized || !gfx->hal) return; - pxl8_gfx_resolve(gfx); + if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) { + pxl8_gfx_resolve(gfx); + gfx->hal->upload_texture( + gfx->platform_data, + gfx->output, + gfx->framebuffer_width, + gfx->framebuffer_height, + PXL8_PIXEL_RGBA, + NULL + ); + return; + } + + u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; + u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); gfx->hal->upload_texture( gfx->platform_data, - gfx->output, + framebuffer, gfx->framebuffer_width, gfx->framebuffer_height, - 4, - NULL + gfx->pixel_mode, + colors ); } @@ -604,7 +639,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo if (is_1to1_scale && is_unclipped && !is_flipped) { const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; - pxl8_blit(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); + pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); } else { for (i32 py = 0; py < draw_height; py++) { for (i32 px = 0; px < draw_width; px++) { @@ -617,7 +652,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo i32 src_idx = src_y * atlas_width + src_x; i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); - framebuffer[dest_idx] = pxl8_blit_mask(atlas_pixels[src_idx], framebuffer[dest_idx]); + framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); } } } diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index 51267b7..ad13479 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -36,7 +36,7 @@ typedef enum pxl8_gfx_effect { extern "C" { #endif -pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_resolution resolution); +pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution); void pxl8_gfx_destroy(pxl8_gfx* gfx); void pxl8_gfx_present(pxl8_gfx* gfx); @@ -46,12 +46,14 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color); pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); -u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx); +u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); +u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx); u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx); pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx); pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx); +pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath); diff --git a/src/gfx/pxl8_render.c b/src/gfx/pxl8_render.c index 31003de..00c3362 100644 --- a/src/gfx/pxl8_render.c +++ b/src/gfx/pxl8_render.c @@ -83,7 +83,7 @@ static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) { return best_idx; } -static u8 blend_colors( +static u8 blend_indexed( const pxl8_gfx_pipeline_desc* pipeline, u8 src, u8 dst, @@ -540,7 +540,7 @@ static void rasterize_triangle( if (color != 0) { u8 out_color = color; if (blend_enabled) { - out_color = blend_colors(pipeline, color, prow[px], palette); + out_color = blend_indexed(pipeline, color, prow[px], palette); } prow[px] = out_color; @@ -586,7 +586,7 @@ static void rasterize_triangle( if (color != 0) { u8 out_color = color; if (blend_enabled) { - out_color = blend_colors(pipeline, color, prow[px], palette); + out_color = blend_indexed(pipeline, color, prow[px], palette); } prow[px] = out_color; diff --git a/src/gfx/pxl8_tilemap.c b/src/gfx/pxl8_tilemap.c index 33ceef2..263b65c 100644 --- a/src/gfx/pxl8_tilemap.c +++ b/src/gfx/pxl8_tilemap.c @@ -17,6 +17,7 @@ struct pxl8_tilesheet { u32 tiles_per_row; u32 total_tiles; u32 width; + pxl8_pixel_mode pixel_mode; u32 ref_count; pxl8_tile_animation* animations; u32 animation_count; @@ -546,6 +547,7 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u tilemap->tilesheet->height = tilesheet_height; tilemap->tilesheet->tiles_per_row = tiles_per_row; tilemap->tilesheet->total_tiles = tileset->tile_count; + tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid); tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool)); diff --git a/src/gfx/pxl8_tilesheet.c b/src/gfx/pxl8_tilesheet.c index 1782220..9f41cd3 100644 --- a/src/gfx/pxl8_tilesheet.c +++ b/src/gfx/pxl8_tilesheet.c @@ -4,6 +4,7 @@ #include #include "pxl8_ase.h" +#include "pxl8_color.h" #include "pxl8_gfx.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -19,6 +20,7 @@ struct pxl8_tilesheet { u32 total_tiles; u32 width; + pxl8_pixel_mode pixel_mode; u32 ref_count; pxl8_tile_animation* animations; @@ -104,10 +106,14 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, tilesheet->height = height; tilesheet->tiles_per_row = width / tilesheet->tile_size; tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size); + tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx); + u32 pixel_count = width * height; u16 ase_depth = ase_file.header.color_depth; + bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR); - tilesheet->data = pxl8_malloc(pixel_count); + usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode); + tilesheet->data = pxl8_malloc(data_size); if (!tilesheet->data) { pxl8_ase_destroy(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; @@ -116,10 +122,34 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) { const u8* src = ase_file.frames[0].pixels; - if (ase_depth == 8) { + if (ase_depth == 8 && !gfx_hicolor) { + memcpy(tilesheet->data, src, pixel_count); + } else if (ase_depth == 32 && gfx_hicolor) { + u16* dst = (u16*)tilesheet->data; + const u32* rgba = (const u32*)src; + for (u32 i = 0; i < pixel_count; i++) { + u32 c = rgba[i]; + u8 a = (c >> 24) & 0xFF; + if (a == 0) { + dst[i] = 0; + } else { + dst[i] = pxl8_rgba32_to_rgb565(c); + } + } + } else if (ase_depth == 8 && gfx_hicolor) { + pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed"); + tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; + u8* new_data = pxl8_realloc(tilesheet->data, pixel_count); + if (!new_data) { + pxl8_free(tilesheet->data); + tilesheet->data = NULL; + pxl8_ase_destroy(&ase_file); + return PXL8_ERROR_OUT_OF_MEMORY; + } + tilesheet->data = new_data; memcpy(tilesheet->data, src, pixel_count); } else { - pxl8_error("Unsupported ASE color depth %d (expected indexed 8-bit)", ase_depth); + pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth); pxl8_free(tilesheet->data); tilesheet->data = NULL; pxl8_ase_destroy(&ase_file); @@ -136,6 +166,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, } u32 valid_tiles = 0; + bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR); for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) { u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size; @@ -145,9 +176,16 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) { for (u32 px = 0; px < tilesheet->tile_size; px++) { u32 idx = (tile_y + py) * width + (tile_x + px); - if (tilesheet->data[idx] != 0) { - has_content = true; - break; + if (is_hicolor) { + if (((u16*)tilesheet->data)[idx] != 0) { + has_content = true; + break; + } + } else { + if (tilesheet->data[idx] != 0) { + has_content = true; + break; + } } } } @@ -274,6 +312,7 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row; u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row; + u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode); for (u32 py = 0; py < tilesheet->tile_size; py++) { for (u32 px = 0; px < tilesheet->tile_size; px++) { @@ -281,7 +320,12 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i u32 dst_x = tile_x * tilesheet->tile_size + px; u32 dst_y = tile_y * tilesheet->tile_size + py; u32 dst_idx = dst_y * tilesheet->width + dst_x; - tilesheet->data[dst_idx] = pixels[src_idx]; + + if (bytes_per_pixel == 2) { + ((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx]; + } else { + tilesheet->data[dst_idx] = pixels[src_idx]; + } } } diff --git a/src/gfx/pxl8_transition.c b/src/gfx/pxl8_transition.c index 5c16266..ba76e1d 100644 --- a/src/gfx/pxl8_transition.c +++ b/src/gfx/pxl8_transition.c @@ -170,7 +170,11 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { i32 block_size = (i32)(max_block_size * progress); if (block_size < 1) block_size = 1; - if (!pxl8_gfx_framebuffer(gfx)) break; + pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx); + bool has_fb = (mode == PXL8_PIXEL_HICOLOR) + ? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL) + : (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL); + if (!has_fb) break; for (i32 y = 0; y < height; y += block_size) { for (i32 x = 0; x < width; x += block_size) { diff --git a/src/hal/pxl8_hal_sdl3.c b/src/hal/pxl8_hal_sdl3.c index 58b8d33..581b257 100644 --- a/src/hal/pxl8_hal_sdl3.c +++ b/src/hal/pxl8_hal_sdl3.c @@ -125,9 +125,16 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, ctx->rgba_buffer_size = pixel_count; } - const u8* pixels8 = (const u8*)pixels; - for (u32 i = 0; i < pixel_count; i++) { - ctx->rgba_buffer[i] = palette[pixels8[i]]; + if (bpp == 2) { + const u16* pixels16 = (const u16*)pixels; + for (u32 i = 0; i < pixel_count; i++) { + ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]); + } + } else { + const u8* pixels8 = (const u8*)pixels; + for (u32 i = 0; i < pixel_count; i++) { + ctx->rgba_buffer[i] = palette[pixels8[i]]; + } } SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4); diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index f2eab50..152ef84 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -64,10 +64,6 @@ function World:init_local_player(x, y, z) C.pxl8_world_init_local_player(self._ptr, x, y, z) end -function World:set_look(yaw, pitch) - C.pxl8_world_set_look(self._ptr, yaw, pitch or 0) -end - function World:local_player() local ptr = C.pxl8_world_local_player(self._ptr) if ptr == nil then return nil end diff --git a/src/script/pxl8_script.c b/src/script/pxl8_script.c index ace4c1f..3685650 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -1214,6 +1214,12 @@ static pxl8_resolution parse_resolution(const char* str) { return PXL8_RESOLUTION_640x360; } +static pxl8_pixel_mode parse_pixel_mode(const char* str) { + if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED; + if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR; + return PXL8_PIXEL_INDEXED; +} + pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) { if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER; @@ -1261,6 +1267,12 @@ pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) } lua_pop(script->L, 1); + lua_getfield(script->L, -1, "pixel-mode"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1))); + } + lua_pop(script->L, 1); + lua_getfield(script->L, -1, "window-size"); if (lua_istable(script->L, -1)) { lua_rawgeti(script->L, -1, 1); diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index 07ec16f..f3e27f9 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -27,7 +27,7 @@ static const char* pxl8_ffi_cdefs = "f32 pxl8_get_fps(const pxl8* sys);\n" "\n" "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" -"u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);\n" +"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n" "i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" "u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);\n" "const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n" @@ -437,7 +437,6 @@ static const char* pxl8_ffi_cdefs = "} pxl8_sim_entity;\n" "\n" "void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n" -"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n" "pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n" "\n" "typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index 5333269..854d7f3 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -316,7 +316,6 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED; world->local_player.kind = 0; world->client_tick = 0; - world->pointer_motion = (pxl8_vec2){0}; #ifdef PXL8_ASYNC_THREADS world->render_state[0] = world->local_player; @@ -324,20 +323,6 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { #endif } -void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch) { - if (!world) return; - world->local_player.yaw = yaw; - world->local_player.pitch = pitch; - world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch}; - -#ifdef PXL8_ASYNC_THREADS - world->render_state[0].yaw = yaw; - world->render_state[0].pitch = pitch; - world->render_state[1].yaw = yaw; - world->render_state[1].pitch = pitch; -#endif -} - pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) { if (!world) return NULL; #ifdef PXL8_ASYNC_THREADS diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index 90a1609..bb09a41 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -37,7 +37,6 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_ void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config); void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z); -void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch); pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world); pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos); void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);