use tiled atlas texture sampling, increase shader speed using inv sqrt

This commit is contained in:
asrael 2026-02-04 07:37:20 -06:00
parent e92748c2d9
commit c4226b36fe
34 changed files with 1045 additions and 520 deletions

View file

@ -353,10 +353,10 @@
(pxl8.push_target)
(pxl8.begin_frame_3d camera lights {
:ambient 30
:ambient 25
:fog_density 0.0
:celestial_dir [0.5 -0.8 0.3]
:celestial_intensity 0.5})
:celestial_intensity 0.3})
(pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18)
@ -364,7 +364,7 @@
(sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt)
(pxl8.clear_depth)
(world:set_wireframe (menu.is-wireframe))
(pxl8.set_wireframe (menu.is-wireframe))
(world:render [smooth-cam-x eye-y smooth-cam-z])
(when chunk

View file

@ -136,18 +136,18 @@
(set selected-item nil)))
(fn draw-gfx-panel []
(pxl8.gui_window 200 100 240 280 "GFX")
(pxl8.gui_window 200 60 240 220 "GFX")
(let [baked-label (if baked-lighting "Baked Lighting: On" "Baked Lighting: Off")]
(when (menu-button 40 215 147 210 30 baked-label)
(when (menu-button 40 215 107 210 24 baked-label)
(set baked-lighting (not baked-lighting))))
(let [dynamic-label (if dynamic-lighting "Dynamic Lighting: On" "Dynamic Lighting: Off")]
(when (menu-button 41 215 182 210 30 dynamic-label)
(when (menu-button 41 215 134 210 24 dynamic-label)
(set dynamic-lighting (not dynamic-lighting))))
(pxl8.gui_label 215 220 (.. "Render: " render-distance) 15)
(let [(changed new-val) (gui:slider_int 30 215 235 210 16 render-distance 1 8)]
(pxl8.gui_label 215 162 (.. "Render: " render-distance) 15)
(let [(changed new-val) (gui:slider_int 30 215 175 210 14 render-distance 1 8)]
(when changed
(set render-distance new-val)
(let [w (world-mod.World.get)
@ -156,14 +156,14 @@
(when n (n:set_chunk_settings new-val sim-distance)))))
(let [tex-label (if textures "Textures: On" "Textures: Off")]
(when (menu-button 42 215 260 210 30 tex-label)
(when (menu-button 42 215 194 210 24 tex-label)
(set textures (not textures))))
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
(when (menu-button 43 215 295 210 30 wire-label)
(when (menu-button 43 215 221 210 24 wire-label)
(set wireframe (not wireframe))))
(when (menu-button 32 215 340 210 30 "Back")
(when (menu-button 32 215 248 210 24 "Back")
(set current-panel :main)
(set selected-item nil)))

88
pxl8.sh
View file

@ -8,7 +8,7 @@ if command -v ccache >/dev/null 2>&1; then
CC="ccache $CC"
fi
CFLAGS="-std=c23 -Wall -Wextra"
CFLAGS="-std=c23 -Wall -Wextra -Wno-missing-braces"
LIBS="-lm"
MODE="debug"
BUILDDIR=".build"
@ -170,8 +170,8 @@ compile_source_file() {
compile_shaders() {
local build_mode="$1"
local shader_dir="src/gfx/shaders/cpu"
local so_dir=".build/shaders/cpu"
local obj_dir=".build/shaders/cpu/obj"
local so_dir=".build/$build_mode/shaders/cpu"
local obj_dir=".build/$build_mode/shaders/cpu/obj"
if [[ ! -d "$shader_dir" ]]; then
return 0
@ -240,6 +240,7 @@ print_usage() {
echo " clean Remove build artifacts"
echo " help Show this help message"
echo " install Install pxl8 to ~/.local/bin"
echo " profile Profile with perf and generate flamegraph (Linux)"
echo " run Build and run pxl8 (optional: cart.pxc or folder)"
echo " update Download/update all dependencies"
echo " vendor Fetch source for dependencies (ex. SDL3)"
@ -248,6 +249,7 @@ print_usage() {
echo " --all Clean both build artifacts and dependencies"
echo " --cache Clear ccache (use with clean)"
echo " --deps Clean only dependencies"
echo " --duration=N Profile duration in seconds (default: 30)"
echo " --release Build/run/clean in release mode (default: debug)"
}
@ -324,6 +326,20 @@ update_fennel() {
fi
}
update_flamegraph() {
print_info "Fetching FlameGraph"
if [[ -d "lib/FlameGraph/.git" ]]; then
cd lib/FlameGraph && git pull --quiet origin master
cd - > /dev/null
else
rm -rf lib/FlameGraph
git clone --quiet https://github.com/brendangregg/FlameGraph.git lib/FlameGraph
fi
print_info "Updated FlameGraph"
}
update_linenoise() {
print_info "Fetching linenoise"
@ -690,6 +706,72 @@ case "$COMMAND" in
bash tools/aseprite/pxl8-ase.sh "$@"
;;
profile)
if [[ "$(uname)" != "Linux" ]]; then
print_error "Profiling with perf is only supported on Linux"
exit 1
fi
if ! command -v perf >/dev/null 2>&1; then
print_error "perf not found. Install linux-tools or perf package."
exit 1
fi
if [[ ! -d "lib/FlameGraph" ]]; then
mkdir -p lib
update_flamegraph
fi
"$0" build || exit 1
PROFILE_DIR=".build/debug/profile"
mkdir -p "$PROFILE_DIR"
CART=""
PERF_DURATION=30
for arg in "$@"; do
if [[ "$arg" =~ ^--duration=([0-9]+)$ ]]; then
PERF_DURATION="${BASH_REMATCH[1]}"
elif [[ "$arg" != "--release" ]] && [[ -z "$CART" ]]; then
CART="$arg"
fi
done
[[ -z "$CART" ]] && CART="demo"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
PERF_DATA="$PROFILE_DIR/perf_${TIMESTAMP}.data"
PERF_SCRIPT="$PROFILE_DIR/perf_${TIMESTAMP}.perf"
FOLDED="$PROFILE_DIR/perf_${TIMESTAMP}.folded"
SVG="$PROFILE_DIR/flamegraph_${TIMESTAMP}.svg"
print_info "Starting server..."
./bin/debug/pxl8d &
SERVER_PID=$!
sleep 0.5
trap "kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null" EXIT
print_info "Profiling pxl8 for ${PERF_DURATION}s (Ctrl+C to stop early)..."
perf record -F 99 -g --call-graph dwarf -o "$PERF_DATA" -- \
timeout "${PERF_DURATION}s" ./bin/debug/pxl8 "$CART" 2>/dev/null || true
print_info "Processing profile data..."
perf script -i "$PERF_DATA" > "$PERF_SCRIPT"
print_info "Generating flamegraph..."
lib/FlameGraph/stackcollapse-perf.pl "$PERF_SCRIPT" > "$FOLDED"
lib/FlameGraph/flamegraph.pl --cp --colors orange --title "pxl8 profile" "$FOLDED" > "$SVG"
rm -f "$PERF_DATA" "$PERF_SCRIPT" "$FOLDED"
print_info "Flamegraph: $SVG"
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$SVG" 2>/dev/null &
fi
;;
help|--help|-h|"")
print_usage
;;

View file

@ -36,9 +36,8 @@ fn main() {
let bindings = bindgen::Builder::default()
.header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap())
.header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap())
.header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap())
.header(pxl8_src.join("math/pxl8_noise.h").to_str().unwrap())
.header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap())
.clang_arg(format!("-I{}", pxl8_src.join("bsp").display()))
.clang_arg(format!("-I{}", pxl8_src.join("core").display()))
.clang_arg(format!("-I{}", pxl8_src.join("math").display()))
@ -50,6 +49,11 @@ fn main() {
.blocklist_item("FP_ZERO")
.blocklist_item("FP_SUBNORMAL")
.blocklist_item("FP_NORMAL")
.blocklist_type("pxl8_vec2")
.blocklist_type("pxl8_vec3")
.blocklist_type("pxl8_vec4")
.blocklist_type("pxl8_mat4")
.raw_line("pub use crate::math::{pxl8_vec2, pxl8_vec3, pxl8_vec4, pxl8_mat4};")
.clang_arg("-DPXL8_NO_SIMD")
.use_core()
.rustified_enum(".*")

View file

@ -101,6 +101,38 @@ impl Default for CellPortals {
}
}
impl Clone for Face {
fn clone(&self) -> Self {
Self {
first_edge: self.first_edge,
lightmap_offset: self.lightmap_offset,
num_edges: self.num_edges,
plane_id: self.plane_id,
side: self.side,
styles: self.styles,
material_id: self.material_id,
aabb_min: self.aabb_min,
aabb_max: self.aabb_max,
}
}
}
impl Clone for Plane {
fn clone(&self) -> Self {
Self {
dist: self.dist,
normal: self.normal,
type_: self.type_,
}
}
}
impl Clone for Vertex {
fn clone(&self) -> Self {
Self { position: self.position }
}
}
pub struct Bsp {
inner: pxl8_bsp,
pub cell_portals: Box<[CellPortals]>,

View file

@ -1,8 +1,43 @@
use core::ops::{Add, Mul, Sub};
use crate::pxl8::pxl8_vec3;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
pub type Vec3 = pxl8_vec3;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Vec4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Mat4 {
pub m: [f32; 16],
}
#[allow(non_camel_case_types)]
pub type pxl8_vec2 = Vec2;
#[allow(non_camel_case_types)]
pub type pxl8_vec3 = Vec3;
#[allow(non_camel_case_types)]
pub type pxl8_vec4 = Vec4;
#[allow(non_camel_case_types)]
pub type pxl8_mat4 = Mat4;
pub const VEC3_ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
pub const VEC3_Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 };

View file

@ -6,6 +6,7 @@ use libm::sqrtf;
use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex};
use crate::math::{Vec3, Vec3Ext};
use crate::pxl8::{pxl8_vec3_cross, pxl8_vec3_dot, pxl8_vec3_normalize, pxl8_vec3_scale, pxl8_vec3_add, pxl8_vec3_sub};
pub const CELL_SIZE: f32 = 64.0;
pub const WALL_HEIGHT: f32 = 128.0;
@ -402,36 +403,139 @@ fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) {
bsp.visdata = visdata;
}
const AO_NUM_SAMPLES: usize = 16;
const AO_RAY_LENGTH: f32 = 48.0;
fn generate_hemisphere_samples(normal: Vec3) -> [Vec3; AO_NUM_SAMPLES] {
let tangent = if normal.y.abs() < 0.9 {
unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(0.0, 1.0, 0.0))) }
} else {
unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(1.0, 0.0, 0.0))) }
};
let bitangent = unsafe { pxl8_vec3_cross(normal, tangent) };
let mut samples = [Vec3::new(0.0, 0.0, 0.0); AO_NUM_SAMPLES];
for i in 0..AO_NUM_SAMPLES {
let phi = (i as f32 / AO_NUM_SAMPLES as f32) * core::f32::consts::TAU;
let theta = ((i as f32 + 0.5) / AO_NUM_SAMPLES as f32) * (core::f32::consts::PI * 0.45);
let (sin_phi, cos_phi) = (libm::sinf(phi), libm::cosf(phi));
let (sin_theta, cos_theta) = (libm::sinf(theta), libm::cosf(theta));
let local_x = sin_theta * cos_phi;
let local_y = cos_theta;
let local_z = sin_theta * sin_phi;
unsafe {
let t_contrib = pxl8_vec3_scale(tangent, local_x);
let n_contrib = pxl8_vec3_scale(normal, local_y);
let b_contrib = pxl8_vec3_scale(bitangent, local_z);
samples[i] = pxl8_vec3_normalize(pxl8_vec3_add(pxl8_vec3_add(t_contrib, n_contrib), b_contrib));
}
}
samples
}
fn ray_triangle_intersect(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3, max_dist: f32) -> bool {
let edge1 = unsafe { pxl8_vec3_sub(v1, v0) };
let edge2 = unsafe { pxl8_vec3_sub(v2, v0) };
let h = unsafe { pxl8_vec3_cross(dir, edge2) };
let a = unsafe { pxl8_vec3_dot(edge1, h) };
if a > -0.0001 && a < 0.0001 {
return false;
}
let f = 1.0 / a;
let s = unsafe { pxl8_vec3_sub(origin, v0) };
let u = f * unsafe { pxl8_vec3_dot(s, h) };
if u < 0.0 || u > 1.0 {
return false;
}
let q = unsafe { pxl8_vec3_cross(s, edge1) };
let v = f * unsafe { pxl8_vec3_dot(dir, q) };
if v < 0.0 || u + v > 1.0 {
return false;
}
let t = f * unsafe { pxl8_vec3_dot(edge2, q) };
t > 0.001 && t < max_dist
}
fn compute_vertex_ao(bsp: &BspBuilder, pos: Vec3, normal: Vec3) -> f32 {
let samples = generate_hemisphere_samples(normal);
let offset_pos = unsafe { pxl8_vec3_add(pos, pxl8_vec3_scale(normal, 0.5)) };
let mut occluded = 0;
for sample_dir in &samples {
'face_loop: for face in &bsp.faces {
if face.num_edges < 3 {
continue;
}
let mut verts = [Vec3::new(0.0, 0.0, 0.0); 4];
let mut num_verts = 0usize;
for e in 0..face.num_edges.min(4) {
let surfedge_idx = (face.first_edge + e as u32) as usize;
if surfedge_idx >= bsp.surfedges.len() {
continue;
}
let edge_idx = bsp.surfedges[surfedge_idx];
let vert_idx = if edge_idx >= 0 {
let ei = edge_idx as usize;
if ei >= bsp.edges.len() { continue; }
bsp.edges[ei].vertex[0] as usize
} else {
let ei = (-edge_idx) as usize;
if ei >= bsp.edges.len() { continue; }
bsp.edges[ei].vertex[1] as usize
};
if vert_idx < bsp.vertices.len() {
verts[num_verts] = bsp.vertices[vert_idx].position;
num_verts += 1;
}
}
if num_verts >= 3 {
if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[1], verts[2], AO_RAY_LENGTH) {
occluded += 1;
break 'face_loop;
}
if num_verts == 4 {
if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[2], verts[3], AO_RAY_LENGTH) {
occluded += 1;
break 'face_loop;
}
}
}
}
}
1.0 - (occluded as f32 / AO_NUM_SAMPLES as f32)
}
fn compute_vertex_light(
pos: Vec3,
normal: Vec3,
lights: &[LightSource],
ambient: f32,
) -> f32 {
let mut total = ambient;
let mut total = 0.0;
for light in lights {
let to_light = Vec3::new(
light.position.x - pos.x,
light.position.y - pos.y,
light.position.z - pos.z,
);
let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
let dist = sqrtf(dist_sq).max(1.0);
let to_light = unsafe { pxl8_vec3_sub(light.position, pos) };
let dist = unsafe { pxl8_vec3_dot(to_light, to_light) };
let dist = sqrtf(dist).max(1.0);
if dist > light.radius {
continue;
}
let inv_dist = 1.0 / dist;
let light_dir = Vec3::new(
to_light.x * inv_dist,
to_light.y * inv_dist,
to_light.z * inv_dist,
);
let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0);
let light_dir = unsafe { pxl8_vec3_normalize(to_light) };
let ndotl = unsafe { pxl8_vec3_dot(normal, light_dir) }.max(0.0);
let attenuation = (1.0 - dist / light.radius).max(0.0);
let attenuation = attenuation * attenuation;
@ -442,12 +546,13 @@ fn compute_vertex_light(
total.min(1.0)
}
fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) {
fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) {
if bsp.vertices.is_empty() {
return;
}
bsp.vertex_lights = vec![0u32; bsp.vertices.len()];
let mut vertex_normals: Vec<Option<Vec3>> = vec![None; bsp.vertices.len()];
for f in 0..bsp.faces.len() {
let face = &bsp.faces[f];
@ -478,13 +583,27 @@ fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], amb
continue;
}
let pos = bsp.vertices[vert_idx].position;
let light = compute_vertex_light(pos, normal, lights, ambient);
vertex_normals[vert_idx] = Some(normal);
let light_byte = (light * 255.0) as u8;
bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF;
let pos = bsp.vertices[vert_idx].position;
let direct = compute_vertex_light(pos, normal, lights);
let direct_byte = ((direct * 255.0).min(255.0)) as u8;
bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0x00FFFFFF) | ((direct_byte as u32) << 24);
}
}
for vert_idx in 0..bsp.vertices.len() {
let normal = match vertex_normals[vert_idx] {
Some(n) => n,
None => continue,
};
let pos = bsp.vertices[vert_idx].position;
let ao = compute_vertex_ao(bsp, pos, normal);
let ao_byte = ((ao * 255.0).min(255.0)) as u8;
bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0xFF00FFFF) | ((ao_byte as u32) << 16);
}
}
fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) {
@ -926,17 +1045,27 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
grid_to_bsp(&mut bsp, &grid);
let light_height = 80.0;
let lights: Vec<LightSource> = rooms.iter().map(|room| {
let fireball_pos = Vec3::new(384.0, light_height, 324.0);
let fireball_exclusion_radius = 150.0;
let lights: Vec<LightSource> = rooms.iter().filter_map(|room| {
let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE;
let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE;
LightSource {
position: Vec3::new(cx, light_height, cy),
intensity: 0.8,
radius: 300.0,
let cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE;
let dx = cx - fireball_pos.x;
let dz = cz - fireball_pos.z;
if dx * dx + dz * dz < fireball_exclusion_radius * fireball_exclusion_radius {
return None;
}
Some(LightSource {
position: Vec3::new(cx, light_height, cz),
intensity: 1.8,
radius: 160.0,
})
}).collect();
compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1);
compute_bsp_vertex_lighting(&mut bsp, &lights);
bsp.into()
}

View file

@ -28,6 +28,20 @@ impl Default for Entity {
}
}
impl Clone for Entity {
fn clone(&self) -> Self {
Self {
pos: self.pos,
vel: self.vel,
yaw: self.yaw,
pitch: self.pitch,
flags: self.flags,
kind: self.kind,
_pad: self._pad,
}
}
}
pub struct Simulation {
pub entities: Vec<Entity>,
pub free_list: Vec<u32>,

View file

@ -118,7 +118,7 @@ static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const
}
static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state,
u32 face_id, pxl8_mesh* mesh) {
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
const pxl8_bsp_face* face = &bsp->faces[face_id];
if (face->num_edges < 3) return;
@ -179,7 +179,11 @@ static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_stat
u8 light = 255;
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF;
u32 packed = bsp->vertex_lights[vert_idx];
u8 direct = (packed >> 24) & 0xFF;
u8 ao = (packed >> 16) & 0xFF;
f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao;
light = (u8)(combined > 255.0f ? 255.0f : combined);
}
pxl8_vertex vtx = {
@ -230,7 +234,7 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return;
collect_face_to_mesh(bsp, NULL, face_id, mesh);
collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx));
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
@ -272,7 +276,7 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh);
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
}
if (mesh->index_count > 0 && current_material < state->num_materials) {
@ -395,7 +399,7 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh);
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
}
}
@ -445,10 +449,3 @@ void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const
state->materials[material_id].u_offset = u_offset;
state->materials[material_id].v_offset = v_offset;
}
void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe) {
if (!state || !state->materials) return;
for (u32 i = 0; i < state->num_materials; i++) {
state->materials[i].wireframe = wireframe;
}
}

View file

@ -23,7 +23,6 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id,
const pxl8_gfx_material* material);
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id,
const pxl8_gfx_material* material);
void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe);
#ifdef __cplusplus
}

View file

@ -2,6 +2,14 @@
#include <string.h>
#if defined(__GNUC__) || defined(__clang__)
#define pxl8_likely(x) __builtin_expect(!!(x), 1)
#define pxl8_unlikely(x) __builtin_expect(!!(x), 0)
#else
#define pxl8_likely(x) (x)
#define pxl8_unlikely(x) (x)
#endif
#ifndef pxl8_min
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
#endif

View file

@ -6,10 +6,10 @@ typedef struct pxl8_atlas pxl8_atlas;
typedef struct pxl8_atlas_entry {
bool active;
i32 h, w, x, y;
u8 log2_h, log2_w;
u32 texture_id;
i32 x, y, w, h;
u32 tiled_base;
u8 log2_w;
} pxl8_atlas_entry;
static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) {

View file

@ -34,16 +34,9 @@ typedef struct pxl8_target_entry {
} pxl8_target_entry;
#define PXL8_MAX_FRAME_RESOURCES 512
#define PXL8_TEXTURE_CACHE_SIZE 256
#define PXL8_STREAM_VB_SIZE (256 * 1024)
#define PXL8_STREAM_IB_SIZE (512 * 1024)
typedef struct pxl8_texture_cache_entry {
u32 texture_id;
pxl8_gfx_texture handle;
bool valid;
} pxl8_texture_cache_entry;
typedef struct pxl8_frame_resources {
pxl8_gfx_texture textures[PXL8_MAX_FRAME_RESOURCES];
pxl8_gfx_buffer buffers[PXL8_MAX_FRAME_RESOURCES];
@ -85,13 +78,13 @@ struct pxl8_gfx {
pxl8_mat4 view_proj;
pxl8_3d_frame frame;
pxl8_texture_cache_entry texture_cache[PXL8_TEXTURE_CACHE_SIZE];
pxl8_gfx_buffer stream_vb;
pxl8_gfx_buffer stream_ib;
u32 stream_vb_capacity;
u32 stream_ib_capacity;
u32 stream_vb_offset;
u32 stream_ib_offset;
bool wireframe;
};
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
@ -198,26 +191,6 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
return 0;
}
static pxl8_gfx_texture texture_cache_lookup(pxl8_gfx* gfx, u32 texture_id) {
u32 slot = texture_id % PXL8_TEXTURE_CACHE_SIZE;
pxl8_texture_cache_entry* entry = &gfx->texture_cache[slot];
if (entry->valid && entry->texture_id == texture_id) {
return entry->handle;
}
return (pxl8_gfx_texture){PXL8_GFX_INVALID_ID};
}
static void texture_cache_insert(pxl8_gfx* gfx, u32 texture_id, pxl8_gfx_texture handle) {
u32 slot = texture_id % PXL8_TEXTURE_CACHE_SIZE;
pxl8_texture_cache_entry* entry = &gfx->texture_cache[slot];
if (entry->valid && entry->texture_id != texture_id) {
pxl8_destroy_texture(gfx->renderer, entry->handle);
}
entry->texture_id = texture_id;
entry->handle = handle;
entry->valid = true;
}
pxl8_gfx* pxl8_gfx_create(
const pxl8_hal* hal,
void* platform_data,
@ -496,6 +469,16 @@ void pxl8_gfx_present(pxl8_gfx* gfx) {
gfx->hal->present(gfx->platform_data);
}
void pxl8_gfx_reset_stats(pxl8_gfx* gfx) {
if (!gfx || !gfx->renderer) return;
pxl8_renderer_reset_stats(gfx->renderer);
}
const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx) {
if (!gfx || !gfx->renderer) return NULL;
return pxl8_renderer_get_stats(gfx->renderer);
}
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
pxl8_viewport vp = {0};
vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height);
@ -840,44 +823,7 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return;
pxl8_frame_resources* res = &gfx->frame_res;
bool is_wireframe = material->wireframe;
pxl8_gfx_texture tex_handle = (pxl8_gfx_texture){PXL8_GFX_INVALID_ID};
if (!is_wireframe && material->texture_id != UINT32_MAX) {
tex_handle = texture_cache_lookup(gfx, material->texture_id);
if (!pxl8_gfx_handle_valid(tex_handle)) {
const pxl8_atlas_entry* tex_entry = NULL;
if (gfx->atlas) {
tex_entry = pxl8_atlas_get_entry(gfx->atlas, material->texture_id);
}
if (tex_entry && tex_entry->active) {
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
u8* tex_data = pxl8_calloc(tex_entry->w * tex_entry->h, 1);
if (tex_data) {
for (i32 row = 0; row < tex_entry->h; row++) {
memcpy(tex_data + row * tex_entry->w,
atlas_pixels + (tex_entry->y + row) * atlas_width + tex_entry->x,
tex_entry->w);
}
u32 tex_size = (u32)(tex_entry->w * tex_entry->h);
pxl8_gfx_texture_desc tex_desc = {
.width = (u32)tex_entry->w,
.height = (u32)tex_entry->h,
.format = PXL8_GFX_FORMAT_INDEXED8,
.data = { .ptr = tex_data, .size = tex_size },
};
tex_handle = pxl8_create_texture(gfx->renderer, &tex_desc);
pxl8_free(tex_data);
if (pxl8_gfx_handle_valid(tex_handle)) {
texture_cache_insert(gfx, material->texture_id, tex_handle);
}
}
}
}
}
bool is_wireframe = gfx->wireframe;
const char* shader_name = "lit";
if (material->emissive || (!material->dynamic_lighting && !material->per_pixel)) {
@ -885,7 +831,13 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
}
pxl8_gfx_pipeline_desc pipe_desc = {
.blend = { .enabled = false },
.blend = {
.enabled = false,
.src = PXL8_GFX_BLEND_ONE,
.dst = PXL8_GFX_BLEND_ZERO,
.alpha_test = false,
.alpha_ref = 0,
},
.depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS },
.dither = material->dither,
.double_sided = material->double_sided,
@ -893,15 +845,36 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
.rasterizer = { .cull = PXL8_GFX_CULL_BACK, .fill = is_wireframe ? PXL8_GFX_FILL_WIREFRAME : PXL8_GFX_FILL_SOLID },
.shader = pxl8_shader_registry_get(shader_name),
};
switch (material->blend_mode) {
case PXL8_BLEND_ALPHA_TEST:
pipe_desc.blend.alpha_test = true;
pipe_desc.blend.alpha_ref = material->alpha;
break;
case PXL8_BLEND_ALPHA:
pipe_desc.blend.enabled = true;
pipe_desc.blend.src = PXL8_GFX_BLEND_SRC_ALPHA;
pipe_desc.blend.dst = PXL8_GFX_BLEND_INV_SRC_ALPHA;
break;
case PXL8_BLEND_ADDITIVE:
pipe_desc.blend.enabled = true;
pipe_desc.blend.src = PXL8_GFX_BLEND_ONE;
pipe_desc.blend.dst = PXL8_GFX_BLEND_ONE;
break;
case PXL8_BLEND_OPAQUE:
default:
break;
}
pxl8_gfx_pipeline pipeline = pxl8_create_pipeline(gfx->renderer, &pipe_desc);
if (res->pipeline_count < PXL8_MAX_FRAME_RESOURCES) {
res->pipelines[res->pipeline_count++] = pipeline;
}
pxl8_gfx_bindings_desc bind_desc = {
.textures = { tex_handle },
.atlas = gfx->atlas,
.colormap = gfx->colormap,
.palette = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL,
.texture_id = material->texture_id,
};
pxl8_gfx_bindings bindings = pxl8_create_bindings(gfx->renderer, &bind_desc);
if (res->bindings_count < PXL8_MAX_FRAME_RESOURCES) {
@ -1101,3 +1074,15 @@ u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
u8 b = (abgr >> 16) & 0xFF;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled) {
if (gfx) gfx->wireframe = enabled;
}
bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx) {
return gfx ? gfx->wireframe : false;
}
u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx) {
return gfx ? gfx->frame.uniforms.ambient : 0;
}

View file

@ -10,6 +10,7 @@
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_gfx_stats pxl8_gfx_stats;
typedef enum pxl8_gfx_effect {
PXL8_GFX_EFFECT_GLOWS = 0,
@ -25,6 +26,8 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
void pxl8_gfx_reset_stats(pxl8_gfx* gfx);
const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx);
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
@ -62,6 +65,11 @@ void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b);
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index);
void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled);
bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx);
u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

View file

@ -33,7 +33,6 @@ typedef struct pxl8_gfx_material {
bool dynamic_lighting;
bool emissive;
bool per_pixel;
bool wireframe;
} pxl8_gfx_material;
typedef struct pxl8_vertex {

View file

@ -2,6 +2,7 @@
#include "pxl8_atlas.h"
#include "pxl8_colormap.h"
#include "pxl8_dither.h"
#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
@ -11,6 +12,16 @@
#include <stdlib.h>
#include <string.h>
#if PXL8_GFX_ENABLE_STATS
#define STATS_INC(stats, field, val) do { (stats)->field += (val); } while (0)
#define STATS_START() pxl8_get_ticks_ns()
#define STATS_ADD(stats, field, start) do { (stats)->field += pxl8_get_ticks_ns() - (start); } while (0)
#else
#define STATS_INC(stats, field, val) do { (void)(stats); } while (0)
#define STATS_START() 0
#define STATS_ADD(stats, field, start) do { (void)(stats); (void)(start); } while (0)
#endif
typedef struct {
pxl8_vec4 clip_pos;
pxl8_vec3 world_pos;
@ -31,25 +42,118 @@ typedef struct {
i32 y_start, y_end;
f32 inv_total;
u32 target_width, target_height;
i32 clip_min_x, clip_min_y;
i32 clip_max_x, clip_max_y;
} tri_setup;
static inline bool depth_test_pass(pxl8_gfx_compare_func func, u16 src, u16 dst) {
switch (func) {
case PXL8_GFX_COMPARE_NEVER: return false;
case PXL8_GFX_COMPARE_LESS: return src < dst;
case PXL8_GFX_COMPARE_EQUAL: return src == dst;
case PXL8_GFX_COMPARE_LEQUAL: return src <= dst;
case PXL8_GFX_COMPARE_GREATER: return src > dst;
case PXL8_GFX_COMPARE_NOTEQUAL: return src != dst;
case PXL8_GFX_COMPARE_GEQUAL: return src >= dst;
case PXL8_GFX_COMPARE_ALWAYS: return true;
}
return true;
}
static inline f32 blend_factor_value(pxl8_gfx_blend_factor factor, f32 src_a, f32 dst_a) {
switch (factor) {
case PXL8_GFX_BLEND_ZERO: return 0.0f;
case PXL8_GFX_BLEND_ONE: return 1.0f;
case PXL8_GFX_BLEND_SRC_ALPHA: return src_a;
case PXL8_GFX_BLEND_INV_SRC_ALPHA: return 1.0f - src_a;
case PXL8_GFX_BLEND_DST_ALPHA: return dst_a;
case PXL8_GFX_BLEND_INV_DST_ALPHA: return 1.0f - dst_a;
}
return 1.0f;
}
static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) {
if (!palette) return 0;
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
for (u32 i = 1; i < 256; i++) {
u8 pr = palette[i] & 0xFF;
u8 pg = (palette[i] >> 8) & 0xFF;
u8 pb = (palette[i] >> 16) & 0xFF;
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
static u8 blend_indexed(
const pxl8_gfx_pipeline_desc* pipeline,
u8 src,
u8 dst,
const u32* palette,
const u8* colormap
) {
(void)colormap;
if (!pipeline || !pipeline->blend.enabled) return src;
if (src == 0) return dst;
if (!palette) return src;
f32 src_a = src == 0 ? 0.0f : 1.0f;
f32 dst_a = dst == 0 ? 0.0f : 1.0f;
f32 sf = blend_factor_value(pipeline->blend.src, src_a, dst_a);
f32 df = blend_factor_value(pipeline->blend.dst, src_a, dst_a);
if (sf == 1.0f && df == 0.0f) return src;
if (sf == 0.0f && df == 1.0f) return dst;
u8 sr = palette[src] & 0xFF;
u8 sg = (palette[src] >> 8) & 0xFF;
u8 sb = (palette[src] >> 16) & 0xFF;
u8 dr = palette[dst] & 0xFF;
u8 dg = (palette[dst] >> 8) & 0xFF;
u8 db = (palette[dst] >> 16) & 0xFF;
i32 out_r = (i32)(sr * sf + dr * df);
i32 out_g = (i32)(sg * sf + dg * df);
i32 out_b = (i32)(sb * sf + db * df);
if (out_r < 0) out_r = 0;
if (out_g < 0) out_g = 0;
if (out_b < 0) out_b = 0;
if (out_r > 255) out_r = 255;
if (out_g > 255) out_g = 255;
if (out_b > 255) out_b = 255;
return palette_find_closest(palette, (u8)out_r, (u8)out_g, (u8)out_b);
}
static inline pxl8_vec4 vec4_lerp(pxl8_vec4 a, pxl8_vec4 b, f32 t) {
return (pxl8_vec4){
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.z + (b.z - a.z) * t,
a.w + (b.w - a.w) * t
};
}
static raster_vertex lerp_raster_vertex(const raster_vertex* a, const raster_vertex* b, f32 t) {
raster_vertex out;
out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t;
out.clip_pos.y = a->clip_pos.y + (b->clip_pos.y - a->clip_pos.y) * t;
out.clip_pos.z = a->clip_pos.z + (b->clip_pos.z - a->clip_pos.z) * t;
out.clip_pos.w = a->clip_pos.w + (b->clip_pos.w - a->clip_pos.w) * t;
out.world_pos.x = a->world_pos.x + (b->world_pos.x - a->world_pos.x) * t;
out.world_pos.y = a->world_pos.y + (b->world_pos.y - a->world_pos.y) * t;
out.world_pos.z = a->world_pos.z + (b->world_pos.z - a->world_pos.z) * t;
out.normal.x = a->normal.x + (b->normal.x - a->normal.x) * t;
out.normal.y = a->normal.y + (b->normal.y - a->normal.y) * t;
out.normal.z = a->normal.z + (b->normal.z - a->normal.z) * t;
out.u = a->u + (b->u - a->u) * t;
out.v = a->v + (b->v - a->v) * t;
out.color = (u8)(a->color + (b->color - a->color) * t);
out.light = (u8)(a->light + (b->light - a->light) * t);
return out;
return (raster_vertex){
.clip_pos = vec4_lerp(a->clip_pos, b->clip_pos, t),
.world_pos = pxl8_vec3_lerp(a->world_pos, b->world_pos, t),
.normal = pxl8_vec3_lerp(a->normal, b->normal, t),
.u = pxl8_lerp(a->u, b->u, t),
.v = pxl8_lerp(a->v, b->v, t),
.color = (u8)(a->color + (b->color - a->color) * t),
.light = (u8)(a->light + (b->light - a->light) * t),
};
}
static i32 clip_triangle_near(
@ -101,26 +205,33 @@ static i32 clip_triangle_near(
static bool setup_tri(
tri_setup* setup,
const raster_vertex* vo0, const raster_vertex* vo1, const raster_vertex* vo2,
u32 width, u32 height, bool double_sided
i32 viewport_x, i32 viewport_y, u32 viewport_w, u32 viewport_h,
i32 clip_min_x, i32 clip_min_y, i32 clip_max_x, i32 clip_max_y,
pxl8_gfx_cull_mode cull, bool double_sided
) {
f32 hw = (f32)width * 0.5f;
f32 hh = (f32)height * 0.5f;
if (viewport_w == 0 || viewport_h == 0) return false;
setup->p0.x = hw + vo0->clip_pos.x / vo0->clip_pos.w * hw;
setup->p0.y = hh - vo0->clip_pos.y / vo0->clip_pos.w * hh;
f32 hw = (f32)viewport_w * 0.5f;
f32 hh = (f32)viewport_h * 0.5f;
setup->p0.x = (f32)viewport_x + hw + vo0->clip_pos.x / vo0->clip_pos.w * hw;
setup->p0.y = (f32)viewport_y + hh - vo0->clip_pos.y / vo0->clip_pos.w * hh;
setup->p0.z = vo0->clip_pos.z / vo0->clip_pos.w;
setup->p1.x = hw + vo1->clip_pos.x / vo1->clip_pos.w * hw;
setup->p1.y = hh - vo1->clip_pos.y / vo1->clip_pos.w * hh;
setup->p1.x = (f32)viewport_x + hw + vo1->clip_pos.x / vo1->clip_pos.w * hw;
setup->p1.y = (f32)viewport_y + hh - vo1->clip_pos.y / vo1->clip_pos.w * hh;
setup->p1.z = vo1->clip_pos.z / vo1->clip_pos.w;
setup->p2.x = hw + vo2->clip_pos.x / vo2->clip_pos.w * hw;
setup->p2.y = hh - vo2->clip_pos.y / vo2->clip_pos.w * hh;
setup->p2.x = (f32)viewport_x + hw + vo2->clip_pos.x / vo2->clip_pos.w * hw;
setup->p2.y = (f32)viewport_y + hh - vo2->clip_pos.y / vo2->clip_pos.w * hh;
setup->p2.z = vo2->clip_pos.z / vo2->clip_pos.w;
f32 cross = (setup->p1.x - setup->p0.x) * (setup->p2.y - setup->p0.y) -
(setup->p1.y - setup->p0.y) * (setup->p2.x - setup->p0.x);
if (!double_sided && cross >= 0.0f) return false;
if (!double_sided) {
if (cull == PXL8_GFX_CULL_BACK && cross >= 0.0f) return false;
if (cull == PXL8_GFX_CULL_FRONT && cross <= 0.0f) return false;
}
const raster_vertex* sorted[3] = {vo0, vo1, vo2};
@ -142,8 +253,8 @@ static bool setup_tri(
i32 y0_int = (i32)floorf(setup->p0.y);
i32 y2_int = (i32)ceilf(setup->p2.y) - 1;
setup->y_start = y0_int < 0 ? 0 : y0_int;
setup->y_end = y2_int >= (i32)height ? (i32)height - 1 : y2_int;
setup->y_start = y0_int < clip_min_y ? clip_min_y : y0_int;
setup->y_end = y2_int > clip_max_y ? clip_max_y : y2_int;
setup->w_recip.x = 1.0f / sorted[0]->clip_pos.w;
setup->w_recip.y = 1.0f / sorted[1]->clip_pos.w;
@ -171,8 +282,12 @@ static bool setup_tri(
setup->normal = sorted[0]->normal;
setup->inv_total = 1.0f / total_height;
setup->target_width = width;
setup->target_height = height;
setup->target_width = viewport_w;
setup->target_height = viewport_h;
setup->clip_min_x = clip_min_x;
setup->clip_min_y = clip_min_y;
setup->clip_max_x = clip_max_x;
setup->clip_max_y = clip_max_y;
return true;
}
@ -184,11 +299,24 @@ static void rasterize_triangle(
u32* light_accum,
u32 fb_width,
pxl8_shader_fn shader,
const pxl8_gfx_pipeline_desc* pipeline,
const pxl8_shader_bindings* bindings,
const pxl8_shader_uniforms* uniforms
const pxl8_shader_uniforms* uniforms,
pxl8_gfx_stats* stats
) {
const i32 SUBDIV = 16;
if (setup->y_start > setup->y_end) return;
bool depth_test = pipeline && pipeline->depth.test;
bool depth_write = pipeline && pipeline->depth.write;
pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS;
bool alpha_test = pipeline && pipeline->blend.alpha_test;
u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0;
bool blend_enabled = pipeline && pipeline->blend.enabled;
const u32* palette = bindings ? bindings->palette : NULL;
const u8* colormap = bindings ? bindings->colormap : NULL;
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
f32 yf = (f32)y + 0.5f;
f32 alpha = (yf - setup->p0.y) * setup->inv_total;
@ -265,8 +393,8 @@ static void rasterize_triangle(
i32 x_start = (i32)floorf(x_start_fp);
i32 x_end = (i32)ceilf(x_end_fp) - 1;
if (x_start < 0) x_start = 0;
if (x_end >= (i32)setup->target_width) x_end = (i32)setup->target_width - 1;
if (x_start < setup->clip_min_x) x_start = setup->clip_min_x;
if (x_end > setup->clip_max_x) x_end = setup->clip_max_x;
if (x_start > x_end) continue;
f32 span_width = x_end_fp - x_start_fp;
@ -313,18 +441,10 @@ static void rasterize_triangle(
f32 u_end = (uw + duw * (f32)span_len) * pw_end;
f32 v_end = (vw + dvw * (f32)span_len) * pw_end;
f32 l_start_fp = lw * pw_start;
f32 l_end_fp = (lw + dlw * (f32)span_len) * pw_end;
f32 c_start_fp = cw * pw_start;
f32 c_end_fp = (cw + dcw * (f32)span_len) * pw_end;
if (l_start_fp > 255.0f) l_start_fp = 255.0f;
if (l_start_fp < 0.0f) l_start_fp = 0.0f;
if (l_end_fp > 255.0f) l_end_fp = 255.0f;
if (l_end_fp < 0.0f) l_end_fp = 0.0f;
if (c_start_fp > 255.0f) c_start_fp = 255.0f;
if (c_start_fp < 0.0f) c_start_fp = 0.0f;
if (c_end_fp > 255.0f) c_end_fp = 255.0f;
if (c_end_fp < 0.0f) c_end_fp = 0.0f;
f32 l_start_fp = pxl8_clamp(lw * pw_start, 0.0f, 255.0f);
f32 l_end_fp = pxl8_clamp((lw + dlw * (f32)span_len) * pw_end, 0.0f, 255.0f);
f32 c_start_fp = pxl8_clamp(cw * pw_start, 0.0f, 255.0f);
f32 c_end_fp = pxl8_clamp((cw + dcw * (f32)span_len) * pw_end, 0.0f, 255.0f);
f32 wx_start = wxw * pw_start;
f32 wy_start = wyw * pw_start;
@ -352,32 +472,44 @@ static void rasterize_triangle(
f32 wz_a = wz_start;
for (i32 px = x; px <= span_end; px++) {
f32 depth_norm = (z_a + 1.0f) * 0.5f;
if (depth_norm < 0.0f) depth_norm = 0.0f;
if (depth_norm > 1.0f) depth_norm = 1.0f;
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
u16 z16 = (u16)(depth_norm * 65535.0f);
if (z16 < zrow[px]) {
STATS_INC(stats, depth_tests, 1);
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
if (depth_pass) {
STATS_INC(stats, depth_passes, 1);
pxl8_shader_ctx frag_ctx = {
.x = px,
.y = y,
.v_uv = { u_a, v_a },
.v_uv = { { u_a, v_a } },
.v_world = { wx_a, wy_a, wz_a },
.v_normal = setup->normal,
.v_light = l_a / 255.0f,
.v_color = c_a,
.v_depth = z_a,
.bindings = bindings,
.uniforms = uniforms,
.out_light_color = 0,
};
u8 color = shader(&frag_ctx);
u8 color = shader(&frag_ctx, bindings, uniforms);
STATS_INC(stats, shader_calls, 1);
if (!(alpha_test && color <= alpha_ref)) {
if (color != 0) {
prow[px] = color;
u8 out_color = color;
if (blend_enabled) {
out_color = blend_indexed(pipeline, color, prow[px], palette, colormap);
}
prow[px] = out_color;
if (depth_write) {
zrow[px] = z16;
}
STATS_INC(stats, pixels_written, 1);
if (lrow && frag_ctx.out_light_color != 0) {
lrow[px] = frag_ctx.out_light_color;
STATS_INC(stats, light_writes, 1);
}
}
}
}
@ -406,9 +538,45 @@ static void rasterize_triangle(
}
}
static void draw_line_clipped(
u8* fb,
u32 fb_w,
u32 fb_h,
i32 x0,
i32 y0,
i32 x1,
i32 y1,
u8 color,
i32 clip_min_x,
i32 clip_min_y,
i32 clip_max_x,
i32 clip_max_y,
pxl8_gfx_stats* stats
) {
i32 dx = abs(x1 - x0);
i32 dy = -abs(y1 - y0);
i32 sx = x0 < x1 ? 1 : -1;
i32 sy = y0 < y1 ? 1 : -1;
i32 err = dx + dy;
while (true) {
if (x0 >= clip_min_x && x0 <= clip_max_x && y0 >= clip_min_y && y0 <= clip_max_y) {
if (x0 >= 0 && y0 >= 0 && x0 < (i32)fb_w && y0 < (i32)fb_h) {
fb[y0 * (i32)fb_w + x0] = color;
STATS_INC(stats, pixels_written, 1);
}
}
if (x0 == x1 && y0 == y1) break;
i32 e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
#define SLOT_INDEX(id) ((id) & 0xFFFF)
#define SLOT_GEN(id) ((id) >> 16)
#define MAKE_ID(index, gen) (((u32)(gen) << 16) | (u32)(index))
#define NEXT_GEN(gen) ((u16)((gen) == 0xFFFF ? 1 : (gen) + 1))
typedef struct {
pxl8_gfx_bindings_desc desc;
@ -432,18 +600,22 @@ typedef struct {
bool active;
} pass_slot;
#define PXL8_PIPELINE_CACHE_SIZE 64
typedef struct {
u32 hash;
u32 slot_idx;
u32 last_used_frame;
bool valid;
} pipeline_cache_entry;
typedef struct {
pxl8_gfx_pipeline_desc desc;
u16 generation;
bool active;
bool cached;
} pipeline_slot;
typedef struct {
pxl8_gfx_sampler_desc desc;
u16 generation;
bool active;
} sampler_slot;
typedef struct {
void* data;
u32 width;
@ -460,11 +632,13 @@ struct pxl8_renderer {
texture_slot textures[PXL8_GFX_MAX_TEXTURES];
buffer_slot buffers[PXL8_GFX_MAX_BUFFERS];
sampler_slot samplers[PXL8_GFX_MAX_SAMPLERS];
pipeline_slot pipelines[PXL8_GFX_MAX_PIPELINES];
bindings_slot bindings[PXL8_GFX_MAX_BINDINGS];
pass_slot passes[PXL8_GFX_MAX_PASSES];
pipeline_cache_entry pipeline_cache[PXL8_PIPELINE_CACHE_SIZE];
u32 frame_counter;
pxl8_gfx_pass current_pass;
pxl8_gfx_pipeline current_pipeline;
pxl8_gfx_bindings current_bindings;
@ -476,6 +650,7 @@ struct pxl8_renderer {
u32 scissor_w, scissor_h;
pxl8_shader_fn shader;
pxl8_gfx_stats stats;
};
struct pxl8_gfx_cmdbuf {
@ -492,6 +667,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) {
r->viewport_h = height;
r->scissor_w = width;
r->scissor_h = height;
pxl8_renderer_reset_stats(r);
return r;
}
@ -518,6 +694,15 @@ void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn) {
if (r) r->shader = fn;
}
void pxl8_renderer_reset_stats(pxl8_renderer* r) {
if (!r) return;
memset(&r->stats, 0, sizeof(r->stats));
}
const pxl8_gfx_stats* pxl8_renderer_get_stats(const pxl8_renderer* r) {
return r ? &r->stats : NULL;
}
static u32 texture_byte_size(pxl8_gfx_texture_format fmt, u32 w, u32 h) {
switch (fmt) {
case PXL8_GFX_FORMAT_INDEXED8: return w * h;
@ -542,7 +727,7 @@ pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_de
s->height = desc->height;
s->format = desc->format;
s->usage = desc->usage;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->generation = NEXT_GEN(s->generation);
s->active = true;
return (pxl8_gfx_texture){ MAKE_ID(i, s->generation) };
}
@ -569,7 +754,7 @@ pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc*
s->append_pos = 0;
s->type = desc->type;
s->usage = desc->usage;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->generation = NEXT_GEN(s->generation);
s->active = true;
return (pxl8_gfx_buffer){ MAKE_ID(i, s->generation) };
}
@ -578,26 +763,12 @@ pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc*
return (pxl8_gfx_buffer){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_sampler pxl8_create_sampler(pxl8_renderer* r, const pxl8_gfx_sampler_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_SAMPLERS; i++) {
if (!r->samplers[i].active) {
sampler_slot* s = &r->samplers[i];
s->desc = *desc;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->active = true;
return (pxl8_gfx_sampler){ MAKE_ID(i, s->generation) };
}
}
pxl8_error("Out of sampler slots");
return (pxl8_gfx_sampler){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_PIPELINES; i++) {
if (!r->pipelines[i].active) {
pipeline_slot* s = &r->pipelines[i];
s->desc = *desc;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->generation = NEXT_GEN(s->generation);
s->active = true;
return (pxl8_gfx_pipeline){ MAKE_ID(i, s->generation) };
}
@ -611,7 +782,7 @@ pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings
if (!r->bindings[i].active) {
bindings_slot* s = &r->bindings[i];
s->desc = *desc;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->generation = NEXT_GEN(s->generation);
s->active = true;
return (pxl8_gfx_bindings){ MAKE_ID(i, s->generation) };
}
@ -625,7 +796,7 @@ pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc)
if (!r->passes[i].active) {
pass_slot* s = &r->passes[i];
s->desc = *desc;
s->generation = (s->generation + 1) ? s->generation + 1 : 1;
s->generation = NEXT_GEN(s->generation);
s->active = true;
return (pxl8_gfx_pass){ MAKE_ID(i, s->generation) };
}
@ -670,13 +841,6 @@ void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf) {
s->active = false;
}
void pxl8_destroy_sampler(pxl8_renderer* r, pxl8_gfx_sampler smp) {
u32 idx = SLOT_INDEX(smp.id);
if (idx < PXL8_GFX_MAX_SAMPLERS && r->samplers[idx].generation == SLOT_GEN(smp.id)) {
r->samplers[idx].active = false;
}
}
void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip) {
u32 idx = SLOT_INDEX(pip.id);
if (idx < PXL8_GFX_MAX_PIPELINES && r->pipelines[idx].generation == SLOT_GEN(pip.id)) {
@ -739,17 +903,17 @@ u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) {
return r->buffers[SLOT_INDEX(buf.id)].size;
}
void* pxl8_texture_ptr(pxl8_renderer* r, pxl8_gfx_texture tex) {
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return NULL;
return r->textures[SLOT_INDEX(tex.id)].data;
}
u32 pxl8_texture_width(pxl8_renderer* r, pxl8_gfx_texture tex) {
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return 0;
return r->textures[SLOT_INDEX(tex.id)].width;
}
u32 pxl8_texture_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return 0;
return r->textures[SLOT_INDEX(tex.id)].height;
}
@ -759,18 +923,6 @@ pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_textu
return r->textures[SLOT_INDEX(tex.id)].format;
}
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
return pxl8_texture_ptr(r, tex);
}
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex) {
return pxl8_texture_width(r, tex);
}
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
return pxl8_texture_height(r, tex);
}
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) {
pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf));
cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd));
@ -865,6 +1017,9 @@ static void execute_draw(
if (!VALID_PASS(r, r->current_pass)) return;
if (!VALID_PIPELINE(r, r->current_pipeline)) return;
u64 exec_start = STATS_START();
STATS_INC(&r->stats, draw_calls, 1);
buffer_slot* vb = &r->buffers[SLOT_INDEX(cmd->vertex_buffer.id)];
buffer_slot* ib = use_indices ? &r->buffers[SLOT_INDEX(cmd->index_buffer.id)] : NULL;
pass_slot* pass = &r->passes[SLOT_INDEX(r->current_pass.id)];
@ -872,10 +1027,12 @@ static void execute_draw(
if (!VALID_TEX(r, pass->desc.color.texture)) {
pxl8_error("draw: invalid color texture");
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
return;
}
if (!VALID_TEX(r, pass->desc.depth.texture)) {
pxl8_error("draw: invalid depth texture");
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
return;
}
@ -887,6 +1044,42 @@ static void execute_draw(
u32 fb_w = color_tex->width;
u32 fb_h = color_tex->height;
if (r->viewport_w == 0 || r->viewport_h == 0) {
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
return;
}
i32 vp_x = r->viewport_x;
i32 vp_y = r->viewport_y;
u32 vp_w = r->viewport_w;
u32 vp_h = r->viewport_h;
i32 clip_min_x = vp_x;
i32 clip_min_y = vp_y;
i32 clip_max_x = vp_x + (i32)vp_w - 1;
i32 clip_max_y = vp_y + (i32)vp_h - 1;
if (r->scissor_w > 0 && r->scissor_h > 0) {
i32 sc_min_x = r->scissor_x;
i32 sc_min_y = r->scissor_y;
i32 sc_max_x = r->scissor_x + (i32)r->scissor_w - 1;
i32 sc_max_y = r->scissor_y + (i32)r->scissor_h - 1;
if (sc_min_x > clip_min_x) clip_min_x = sc_min_x;
if (sc_min_y > clip_min_y) clip_min_y = sc_min_y;
if (sc_max_x < clip_max_x) clip_max_x = sc_max_x;
if (sc_max_y < clip_max_y) clip_max_y = sc_max_y;
}
if (clip_min_x < 0) clip_min_x = 0;
if (clip_min_y < 0) clip_min_y = 0;
if (clip_max_x >= (i32)fb_w) clip_max_x = (i32)fb_w - 1;
if (clip_max_y >= (i32)fb_h) clip_max_y = (i32)fb_h - 1;
if (clip_min_x > clip_max_x || clip_min_y > clip_max_y) {
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
return;
}
u32* light_accum = NULL;
if (VALID_TEX(r, pass->desc.light_accum.texture)) {
texture_slot* light_tex = &r->textures[SLOT_INDEX(pass->desc.light_accum.texture.id)];
@ -901,7 +1094,10 @@ static void execute_draw(
f32 near = 0.1f;
pxl8_shader_fn shader = pip->desc.shader;
if (!shader) return;
if (!shader) {
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
return;
}
pxl8_shader_bindings shader_bindings = {0};
pxl8_shader_uniforms shader_uniforms = r->current_draw_params.shader;
@ -913,19 +1109,26 @@ static void execute_draw(
shader_bindings.colormap = (const u8*)bnd->desc.colormap;
shader_bindings.palette = bnd->desc.palette;
pxl8_gfx_texture tex0 = bnd->desc.textures[0];
if (tex0.id != PXL8_GFX_INVALID_ID && VALID_TEX(r, tex0)) {
texture_slot* ts = &r->textures[SLOT_INDEX(tex0.id)];
shader_bindings.texture = ts->data;
shader_bindings.tex_width = ts->width;
shader_bindings.tex_height = ts->height;
const pxl8_atlas* atlas = bnd->desc.atlas;
if (atlas && bnd->desc.texture_id != UINT32_MAX) {
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(atlas, bnd->desc.texture_id);
if (entry && entry->active) {
shader_bindings.atlas = pxl8_atlas_get_pixels_tiled(atlas);
shader_bindings.width = (u32)entry->w;
shader_bindings.height = (u32)entry->h;
shader_bindings.use_tiled = true;
shader_bindings.tiled.base = entry->tiled_base;
shader_bindings.tiled.log2_w = entry->log2_w;
}
}
}
bool double_sided = pip->desc.double_sided;
pxl8_gfx_cull_mode cull_mode = pip->desc.rasterizer.cull;
bool is_wireframe = pip->desc.rasterizer.fill == PXL8_GFX_FILL_WIREFRAME;
for (u32 i = cmd->first_index; i < cmd->first_index + cmd->index_count; i += 3) {
STATS_INC(&r->stats, triangles, 1);
u16 i0, i1, i2;
if (use_indices) {
if (i + 2 >= ib->size / sizeof(u16)) break;
@ -956,6 +1159,7 @@ static void execute_draw(
rv1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1);
rv2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2);
if (!is_wireframe) {
pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p0);
pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p1);
pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p2);
@ -978,43 +1182,75 @@ static void execute_draw(
rv0.light = v0->light;
rv1.light = v1->light;
rv2.light = v2->light;
} else {
rv0.world_pos = (pxl8_vec3){0, 0, 0};
rv1.world_pos = (pxl8_vec3){0, 0, 0};
rv2.world_pos = (pxl8_vec3){0, 0, 0};
rv0.normal = (pxl8_vec3){0, 0, 0};
rv1.normal = (pxl8_vec3){0, 0, 0};
rv2.normal = (pxl8_vec3){0, 0, 0};
rv0.u = 0.0f; rv0.v = 0.0f;
rv1.u = 0.0f; rv1.v = 0.0f;
rv2.u = 0.0f; rv2.v = 0.0f;
rv0.color = 0; rv1.color = 0; rv2.color = 0;
rv0.light = 0; rv1.light = 0; rv2.light = 0;
}
raster_vertex clipped[6];
i32 clipped_count = clip_triangle_near(&rv0, &rv1, &rv2, near, clipped);
for (i32 t = 0; t < clipped_count; t += 3) {
STATS_INC(&r->stats, clipped_triangles, 1);
if (is_wireframe) {
f32 hw = (f32)fb_w * 0.5f;
f32 hh = (f32)fb_h * 0.5f;
f32 hw = (f32)vp_w * 0.5f;
f32 hh = (f32)vp_h * 0.5f;
raster_vertex* wv0 = &clipped[t];
raster_vertex* wv1 = &clipped[t+1];
raster_vertex* wv2 = &clipped[t+2];
i32 sx0 = (i32)(hw + wv0->clip_pos.x / wv0->clip_pos.w * hw);
i32 sy0 = (i32)(hh - wv0->clip_pos.y / wv0->clip_pos.w * hh);
i32 sx1 = (i32)(hw + wv1->clip_pos.x / wv1->clip_pos.w * hw);
i32 sy1 = (i32)(hh - wv1->clip_pos.y / wv1->clip_pos.w * hh);
i32 sx2 = (i32)(hw + wv2->clip_pos.x / wv2->clip_pos.w * hw);
i32 sy2 = (i32)(hh - wv2->clip_pos.y / wv2->clip_pos.w * hh);
i32 sx0 = (i32)((f32)vp_x + hw + wv0->clip_pos.x / wv0->clip_pos.w * hw);
i32 sy0 = (i32)((f32)vp_y + hh - wv0->clip_pos.y / wv0->clip_pos.w * hh);
i32 sx1 = (i32)((f32)vp_x + hw + wv1->clip_pos.x / wv1->clip_pos.w * hw);
i32 sy1 = (i32)((f32)vp_y + hh - wv1->clip_pos.y / wv1->clip_pos.w * hh);
i32 sx2 = (i32)((f32)vp_x + hw + wv2->clip_pos.x / wv2->clip_pos.w * hw);
i32 sy2 = (i32)((f32)vp_y + hh - wv2->clip_pos.y / wv2->clip_pos.w * hh);
f32 cross = (f32)(sx1 - sx0) * (f32)(sy2 - sy0) - (f32)(sy1 - sy0) * (f32)(sx2 - sx0);
if (!double_sided) {
if (cull_mode == PXL8_GFX_CULL_BACK && cross >= 0.0f) continue;
if (cull_mode == PXL8_GFX_CULL_FRONT && cross <= 0.0f) continue;
}
u8 wire_color = v0->color ? v0->color : 15;
pxl8_draw_line(r, pass->desc.color.texture, sx0, sy0, sx1, sy1, wire_color);
pxl8_draw_line(r, pass->desc.color.texture, sx1, sy1, sx2, sy2, wire_color);
pxl8_draw_line(r, pass->desc.color.texture, sx2, sy2, sx0, sy0, wire_color);
draw_line_clipped(fb, fb_w, fb_h, sx0, sy0, sx1, sy1, wire_color,
clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats);
draw_line_clipped(fb, fb_w, fb_h, sx1, sy1, sx2, sy2, wire_color,
clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats);
draw_line_clipped(fb, fb_w, fb_h, sx2, sy2, sx0, sy0, wire_color,
clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats);
} else {
tri_setup setup;
if (!setup_tri(&setup, &clipped[t], &clipped[t+1], &clipped[t+2], fb_w, fb_h, double_sided)) {
if (!setup_tri(&setup, &clipped[t], &clipped[t+1], &clipped[t+2],
vp_x, vp_y, vp_w, vp_h,
clip_min_x, clip_min_y, clip_max_x, clip_max_y,
cull_mode, double_sided)) {
continue;
}
rasterize_triangle(&setup, fb, zb, light_accum, fb_w, shader, &shader_bindings, &shader_uniforms);
u64 raster_start = STATS_START();
rasterize_triangle(&setup, fb, zb, light_accum, fb_w, shader, &pip->desc,
&shader_bindings, &shader_uniforms, &r->stats);
STATS_ADD(&r->stats, raster_ns, raster_start);
}
}
}
STATS_ADD(&r->stats, execute_draw_ns, exec_start);
}
void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
u64 submit_start = STATS_START();
for (u32 i = 0; i < cb->count; i++) {
pxl8_gfx_cmd* cmd = &cb->commands[i];
switch (cmd->type) {
@ -1070,10 +1306,7 @@ void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
r->buffers[i].append_pos = 0;
}
}
}
void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
pxl8_submit(r, cb);
STATS_ADD(&r->stats, submit_ns, submit_start);
}
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color) {
@ -1221,7 +1454,7 @@ void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i3
}
}
void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum,
void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum,
const u32* palette, u32* output) {
if (!VALID_TEX(r, color)) return;
texture_slot* cs = &r->textures[SLOT_INDEX(color.id)];
@ -1256,9 +1489,9 @@ void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture lig
bg += (i32)((f32)(lg - 128) * t * 2.0f);
bb += (i32)((f32)(lb - 128) * t * 2.0f);
if (br < 0) br = 0; if (br > 255) br = 255;
if (bg < 0) bg = 0; if (bg > 255) bg = 255;
if (bb < 0) bb = 0; if (bb > 255) bb = 255;
br = pxl8_clamp_byte(br);
bg = pxl8_clamp_byte(bg);
bb = pxl8_clamp_byte(bb);
base = (u32)br | ((u32)bg << 8) | ((u32)bb << 16) | 0xFF000000;
}
@ -1267,8 +1500,3 @@ void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture lig
output[i] = base | 0xFF000000;
}
}
void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum,
const u32* palette, u32* output) {
pxl8_resolve(r, color, light_accum, palette, output);
}

View file

@ -4,6 +4,10 @@
#include "pxl8_render_types.h"
#include "pxl8_shader.h"
#ifndef PXL8_GFX_ENABLE_STATS
#define PXL8_GFX_ENABLE_STATS 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -11,9 +15,25 @@ extern "C" {
typedef struct pxl8_renderer pxl8_renderer;
typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf;
typedef struct pxl8_gfx_stats {
u64 draw_calls;
u64 triangles;
u64 clipped_triangles;
u64 depth_tests;
u64 depth_passes;
u64 shader_calls;
u64 pixels_written;
u64 light_writes;
u64 submit_ns;
u64 execute_draw_ns;
u64 raster_ns;
} pxl8_gfx_stats;
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height);
void pxl8_renderer_destroy(pxl8_renderer* r);
void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn);
void pxl8_renderer_reset_stats(pxl8_renderer* r);
const pxl8_gfx_stats* pxl8_renderer_get_stats(const pxl8_renderer* r);
u32 pxl8_renderer_get_width(const pxl8_renderer* r);
u32 pxl8_renderer_get_height(const pxl8_renderer* r);
@ -22,14 +42,12 @@ pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc);
pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc);
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc);
pxl8_gfx_sampler pxl8_create_sampler(pxl8_renderer* r, const pxl8_gfx_sampler_desc* desc);
pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_desc* desc);
void pxl8_destroy_bindings(pxl8_renderer* r, pxl8_gfx_bindings bnd);
void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf);
void pxl8_destroy_pass(pxl8_renderer* r, pxl8_gfx_pass pass);
void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip);
void pxl8_destroy_sampler(pxl8_renderer* r, pxl8_gfx_sampler smp);
void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex);
void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
@ -39,11 +57,8 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_
void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf);
u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf);
void* pxl8_texture_ptr(pxl8_renderer* r, pxl8_gfx_texture tex);
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_width(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_height(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex);
pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex);
@ -61,7 +76,6 @@ void pxl8_set_viewport(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h);
void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32 first, u32 count, u32 base_vertex);
void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb);
void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb);
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color);
@ -76,8 +90,6 @@ void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y
void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum,
const u32* palette, u32* output);
void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum,
const u32* palette, u32* output);

View file

@ -1,5 +1,6 @@
#pragma once
#include "pxl8_atlas.h"
#include "pxl8_colormap.h"
#include "pxl8_lights.h"
#include "pxl8_math.h"
@ -10,17 +11,14 @@
extern "C" {
#endif
#define PXL8_GFX_MAX_TEXTURES 256
#define PXL8_GFX_MAX_BUFFERS 512
#define PXL8_GFX_MAX_SAMPLERS 32
#define PXL8_GFX_MAX_PIPELINES 128
#define PXL8_GFX_MAX_BINDINGS 256
#define PXL8_GFX_MAX_BUFFERS 512
#define PXL8_GFX_MAX_PASSES 32
#define PXL8_GFX_MAX_BOUND_TEXTURES 4
#define PXL8_GFX_MAX_PIPELINES 128
#define PXL8_GFX_MAX_TEXTURES 256
typedef struct { u32 id; } pxl8_gfx_buffer;
typedef struct { u32 id; } pxl8_gfx_texture;
typedef struct { u32 id; } pxl8_gfx_sampler;
typedef struct { u32 id; } pxl8_gfx_pipeline;
typedef struct { u32 id; } pxl8_gfx_bindings;
typedef struct { u32 id; } pxl8_gfx_pass;
@ -95,17 +93,6 @@ typedef enum pxl8_gfx_store_op {
PXL8_GFX_STORE_DONT_CARE,
} pxl8_gfx_store_op;
typedef enum pxl8_gfx_filter {
PXL8_GFX_FILTER_NEAREST,
PXL8_GFX_FILTER_LINEAR,
} pxl8_gfx_filter;
typedef enum pxl8_gfx_wrap {
PXL8_GFX_WRAP_REPEAT,
PXL8_GFX_WRAP_CLAMP,
PXL8_GFX_WRAP_MIRROR,
} pxl8_gfx_wrap;
typedef struct pxl8_gfx_buffer_desc {
pxl8_gfx_range data;
u32 capacity;
@ -122,13 +109,6 @@ typedef struct pxl8_gfx_texture_desc {
bool render_target;
} pxl8_gfx_texture_desc;
typedef struct pxl8_gfx_sampler_desc {
pxl8_gfx_filter min_filter;
pxl8_gfx_filter mag_filter;
pxl8_gfx_wrap wrap_u;
pxl8_gfx_wrap wrap_v;
} pxl8_gfx_sampler_desc;
typedef pxl8_shader_fn pxl8_gfx_shader;
typedef struct pxl8_gfx_pipeline_desc {
@ -159,10 +139,10 @@ typedef struct pxl8_gfx_pipeline_desc {
} pxl8_gfx_pipeline_desc;
typedef struct pxl8_gfx_bindings_desc {
pxl8_gfx_texture textures[PXL8_GFX_MAX_BOUND_TEXTURES];
pxl8_gfx_sampler samplers[PXL8_GFX_MAX_BOUND_TEXTURES];
const pxl8_atlas* atlas;
const pxl8_colormap* colormap;
const u32* palette;
u32 texture_id;
} pxl8_gfx_bindings_desc;
typedef struct pxl8_gfx_pass_color_attachment {

View file

@ -39,34 +39,42 @@ typedef struct pxl8_shader_uniforms {
u32 lights_count;
bool textures;
f32 time;
bool wireframe;
} pxl8_shader_uniforms;
typedef struct pxl8_shader_bindings {
const u8* colormap;
const u32* palette;
const u8* texture;
u32 tex_width;
u32 tex_height;
const u8* atlas;
u32 width, height;
bool use_tiled;
union {
struct {
u32 stride;
u32 x, y;
} linear;
struct {
u32 base;
u8 log2_w;
} tiled;
};
} pxl8_shader_bindings;
typedef struct pxl8_shader_ctx {
i32 x, y;
pxl8_vec2 v_uv;
pxl8_vec3 v_world;
pxl8_vec3 v_normal;
f32 v_color;
f32 v_light;
f32 v_depth;
const pxl8_shader_bindings* bindings;
const pxl8_shader_uniforms* uniforms;
u32 out_light_color;
} pxl8_shader_ctx;
typedef u8 (*pxl8_shader_fn)(pxl8_shader_ctx* ctx);
typedef u8 (*pxl8_shader_fn)(
pxl8_shader_ctx* ctx,
const pxl8_shader_bindings* bindings,
const pxl8_shader_uniforms* uniforms
);
#ifdef __cplusplus
}

View file

@ -5,28 +5,10 @@
#include "pxl8_shader.h"
#include "pxl8_types.h"
#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline f32 pxl8_abs(f32 x) { return fabsf(x); }
static inline f32 pxl8_ceil(f32 x) { return ceilf(x); }
static inline f32 pxl8_cos(f32 x) { return cosf(x); }
static inline f32 pxl8_exp(f32 x) { return expf(x); }
static inline f32 pxl8_floor(f32 x) { return floorf(x); }
static inline f32 pxl8_log(f32 x) { return logf(x); }
static inline f32 pxl8_pow(f32 x, f32 y) { return powf(x, y); }
static inline f32 pxl8_sin(f32 x) { return sinf(x); }
static inline f32 pxl8_sqrt(f32 x) { return sqrtf(x); }
static inline f32 pxl8_tan(f32 x) { return tanf(x); }
static inline f32 pxl8_min(f32 a, f32 b) { return a < b ? a : b; }
static inline f32 pxl8_max(f32 a, f32 b) { return a > b ? a : b; }
static inline f32 pxl8_clamp(f32 x, f32 lo, f32 hi) { return pxl8_min(pxl8_max(x, lo), hi); }
static inline f32 pxl8_lerp(f32 a, f32 b, f32 t) { return a + (b - a) * t; }
static inline pxl8_vec2 pxl8_swizzle_xy(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.y}; }
static inline pxl8_vec2 pxl8_swizzle_xz(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.z}; }
static inline pxl8_vec2 pxl8_swizzle_xw(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.w}; }
@ -39,27 +21,39 @@ static inline pxl8_vec3 pxl8_swizzle_xyw(pxl8_vec4 v) { return (pxl8_vec3){v.x,
static inline pxl8_vec3 pxl8_swizzle_xzw(pxl8_vec4 v) { return (pxl8_vec3){v.x, v.z, v.w}; }
static inline pxl8_vec3 pxl8_swizzle_yzw(pxl8_vec4 v) { return (pxl8_vec3){v.y, v.z, v.w}; }
static inline u8 pxl8_sample_indexed(pxl8_shader_ctx* ctx, pxl8_vec2 uv) {
const u8* tex = ctx->bindings->texture;
if (!tex) return 0;
u32 w = ctx->bindings->tex_width;
u32 h = ctx->bindings->tex_height;
if (w == 0 || h == 0) return 0;
i32 tx = (i32)(uv.x * (f32)w) & (i32)(w - 1);
i32 ty = (i32)(uv.y * (f32)h) & (i32)(h - 1);
return tex[ty * w + tx];
static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) {
u32 tile_y = v >> 3;
u32 tile_x = u >> 3;
u32 local_y = v & 7;
u32 local_x = u & 7;
return (tile_y << (log2_w + 3)) | (tile_x << 6) | (local_y << 3) | local_x;
}
static inline u8 pxl8_colormap_lookup(pxl8_shader_ctx* ctx, u8 color, u8 light) {
const u8* cm = ctx->bindings->colormap;
static inline u8 pxl8_sample_indexed(const pxl8_shader_bindings* b, pxl8_vec2 uv) {
if (!b || !b->atlas) return 0;
u32 w = b->width;
u32 h = b->height;
if (w == 0 || h == 0) return 0;
u32 u = (u32)(uv.x * (f32)w) & (w - 1);
u32 v = (u32)(uv.y * (f32)h) & (h - 1);
if (b->use_tiled) {
return b->atlas[b->tiled.base + pxl8_tile_addr(u, v, b->tiled.log2_w)];
} else {
return b->atlas[(b->linear.y + v) * b->linear.stride + b->linear.x + u];
}
}
static inline u8 pxl8_colormap_lookup(const pxl8_shader_bindings* b, u8 color, u8 light) {
if (!b) return color;
const u8* cm = b->colormap;
if (!cm) return color;
u32 row = light >> 5;
return cm[(row << 8) | (u32)color];
}
static inline f32 pxl8_light_falloff(pxl8_shader_ctx* ctx, u32 light_idx) {
if (light_idx >= ctx->uniforms->lights_count) return 0.0f;
const pxl8_light* light = &ctx->uniforms->lights[light_idx];
static inline f32 pxl8_light_falloff(const pxl8_shader_ctx* ctx, const pxl8_shader_uniforms* u, u32 light_idx) {
if (!u || light_idx >= u->lights_count) return 0.0f;
const pxl8_light* light = &u->lights[light_idx];
f32 dx = light->position.x - ctx->v_world.x;
f32 dy = light->position.y - ctx->v_world.y;
f32 dz = light->position.z - ctx->v_world.z;
@ -68,9 +62,9 @@ static inline f32 pxl8_light_falloff(pxl8_shader_ctx* ctx, u32 light_idx) {
return 1.0f - dist_sq * light->inv_radius_sq;
}
static inline u32 pxl8_light_color(pxl8_shader_ctx* ctx, u32 light_idx) {
if (light_idx >= ctx->uniforms->lights_count) return 0;
const pxl8_light* light = &ctx->uniforms->lights[light_idx];
static inline u32 pxl8_light_color(const pxl8_shader_uniforms* u, u32 light_idx) {
if (!u || light_idx >= u->lights_count) return 0;
const pxl8_light* light = &u->lights[light_idx];
return (u32)light->r | ((u32)light->g << 8) | ((u32)light->b << 16);
}

View file

@ -2,8 +2,8 @@
#include <string.h>
extern u8 pxl8_shader_lit(pxl8_shader_ctx* ctx);
extern u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx);
extern u8 pxl8_shader_lit(pxl8_shader_ctx* ctx, const pxl8_shader_bindings* bindings, const pxl8_shader_uniforms* uniforms);
extern u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx, const pxl8_shader_bindings* bindings, const pxl8_shader_uniforms* uniforms);
void pxl8_shader_registry_init(void) {}
void pxl8_shader_registry_reload(void) {}

View file

@ -1,13 +1,18 @@
#include "pxl8_macros.h"
#include "pxl8_shader.h"
#include "pxl8_shader_builtins.h"
u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
u8 pxl8_shader_lit(
pxl8_shader_ctx* ctx,
const pxl8_shader_bindings* bindings,
const pxl8_shader_uniforms* uniforms
) {
u8 tex_idx = 0;
if (ctx->bindings && ctx->bindings->texture) {
tex_idx = pxl8_sample_indexed(ctx, ctx->v_uv);
if (tex_idx == 0) return 0;
if (bindings && bindings->atlas) {
tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv);
if (pxl8_unlikely(tex_idx == 0)) return 0;
} else {
if (ctx->uniforms && ctx->uniforms->dither) {
if (uniforms && uniforms->dither) {
tex_idx = pxl8_gfx_dither(ctx->v_color, (u32)ctx->x, (u32)ctx->y);
} else {
f32 clamped = pxl8_clamp(ctx->v_color, 0.0f, 255.0f);
@ -17,24 +22,16 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
f32 light = ctx->v_light;
const pxl8_shader_uniforms* u = ctx->uniforms;
if (u) {
f32 ambient = (f32)u->ambient / 255.0f;
if (uniforms) {
f32 ambient = (f32)uniforms->ambient / 255.0f;
if (ambient > light) light = ambient;
if (u->celestial_intensity > 0.0f) {
f32 dx = u->celestial_dir.x;
f32 dy = u->celestial_dir.y;
f32 dz = u->celestial_dir.z;
f32 len = pxl8_sqrt(dx * dx + dy * dy + dz * dz);
if (len > 0.0001f) {
dx /= len;
dy /= len;
dz /= len;
f32 ndotl = -(ctx->v_normal.x * dx + ctx->v_normal.y * dy + ctx->v_normal.z * dz);
if (uniforms->celestial_intensity > 0.0f) {
f32 ndotl = -(ctx->v_normal.x * uniforms->celestial_dir.x +
ctx->v_normal.y * uniforms->celestial_dir.y +
ctx->v_normal.z * uniforms->celestial_dir.z);
if (ndotl > 0.0f) {
light += ndotl * u->celestial_intensity;
}
light += ndotl * uniforms->celestial_intensity;
}
}
@ -43,16 +40,15 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
f32 dyn_g = 0.0f;
f32 dyn_b = 0.0f;
for (u32 i = 0; i < u->lights_count; i++) {
const pxl8_light* l = &u->lights[i];
for (u32 i = 0; i < uniforms->lights_count; i++) {
const pxl8_light* l = &uniforms->lights[i];
f32 lx = l->position.x - ctx->v_world.x;
f32 ly = l->position.y - ctx->v_world.y;
f32 lz = l->position.z - ctx->v_world.z;
f32 dist_sq = lx * lx + ly * ly + lz * lz;
if (dist_sq >= l->radius_sq) continue;
f32 dist = pxl8_sqrt(dist_sq);
f32 inv_dist = dist > 0.0001f ? (1.0f / dist) : 0.0f;
f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq);
f32 nx = lx * inv_dist;
f32 ny = ly * inv_dist;
f32 nz = lz * inv_dist;
@ -62,6 +58,10 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
f32 falloff = 1.0f - dist_sq * l->inv_radius_sq;
if (falloff <= 0.0f) continue;
if (uniforms->dither && falloff < 0.33f) {
f32 threshold = (PXL8_BAYER_4X4[((u32)ctx->y & 3) * 4 + ((u32)ctx->x & 3)] + 0.5f) * (1.0f / 16.0f);
if (falloff < threshold * 0.33f) continue;
}
f32 strength = ((f32)l->intensity / 255.0f) * falloff * ndotl;
if (strength <= 0.0f) continue;
@ -73,7 +73,7 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
}
if (dyn_strength > 0.0f) {
f32 inv = 1.0f / dyn_strength;
f32 inv = pxl8_fast_rcp(dyn_strength);
u8 r = (u8)pxl8_clamp(dyn_r * inv, 0.0f, 255.0f);
u8 g = (u8)pxl8_clamp(dyn_g * inv, 0.0f, 255.0f);
u8 b = (u8)pxl8_clamp(dyn_b * inv, 0.0f, 255.0f);
@ -88,16 +88,16 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) {
f32 light_f = light * 255.0f;
u8 light_u8 = (u8)light_f;
if (ctx->uniforms && ctx->uniforms->dither) {
if (uniforms && uniforms->dither) {
light_u8 = pxl8_gfx_dither(light_f, (u32)ctx->x, (u32)ctx->y);
}
u8 shaded = pxl8_colormap_lookup(ctx, tex_idx, light_u8);
u8 shaded = pxl8_colormap_lookup(bindings, tex_idx, light_u8);
if (ctx->uniforms && ctx->uniforms->emissive) {
if (uniforms && uniforms->emissive) {
u32 rgb = 0x00FFFFFF;
if (ctx->bindings && ctx->bindings->palette) {
rgb = ctx->bindings->palette[tex_idx] & 0x00FFFFFF;
if (bindings && bindings->palette) {
rgb = bindings->palette[tex_idx] & 0x00FFFFFF;
}
pxl8_set_light_tint(ctx, rgb, 1.0f);
}

View file

@ -1,13 +1,17 @@
#include "pxl8_shader.h"
#include "pxl8_shader_builtins.h"
u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx) {
u8 pxl8_shader_unlit(
pxl8_shader_ctx* ctx,
const pxl8_shader_bindings* bindings,
const pxl8_shader_uniforms* uniforms
) {
u8 tex_idx = 0;
if (ctx->bindings && ctx->bindings->texture) {
tex_idx = pxl8_sample_indexed(ctx, ctx->v_uv);
if (bindings && bindings->atlas) {
tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv);
if (tex_idx == 0) return 0;
} else {
if (ctx->uniforms && ctx->uniforms->dither) {
if (uniforms && uniforms->dither) {
tex_idx = pxl8_gfx_dither(ctx->v_color, (u32)ctx->x, (u32)ctx->y);
} else {
f32 clamped = pxl8_clamp(ctx->v_color, 0.0f, 255.0f);
@ -15,10 +19,10 @@ u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx) {
}
}
if (ctx->uniforms && ctx->uniforms->emissive) {
if (uniforms && uniforms->emissive) {
u32 rgb = 0x00FFFFFF;
if (ctx->bindings && ctx->bindings->palette) {
rgb = ctx->bindings->palette[tex_idx] & 0x00FFFFFF;
if (bindings && bindings->palette) {
rgb = bindings->palette[tex_idx] & 0x00FFFFFF;
}
pxl8_set_light_tint(ctx, rgb, 1.0f);
}

View file

@ -101,6 +101,8 @@ pxl8.draw_line_3d = gfx.draw_line_3d
pxl8.draw_mesh = gfx.draw_mesh
pxl8.end_frame_3d = gfx.end_frame_3d
pxl8.project_points = gfx.project_points
pxl8.set_wireframe = gfx.set_wireframe
pxl8.get_wireframe = gfx.get_wireframe
pxl8.Lights = effects.Lights
pxl8.create_lights = effects.Lights.new

View file

@ -258,7 +258,6 @@ function gfx.draw_mesh(mesh, opts)
emissive = opts.emissive or false,
per_pixel = opts.per_pixel or false,
texture_id = opts.texture or 0xFFFFFFFF,
wireframe = opts.wireframe or false,
})
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
end
@ -272,15 +271,22 @@ function gfx.begin_frame_3d(camera, lights, uniforms)
u.fog_density = uniforms.fog_density or 0.0
u.time = uniforms.time or 0.0
local cx, cy, cz
if uniforms.celestial_dir then
u.celestial_dir.x = uniforms.celestial_dir[1] or 0
u.celestial_dir.y = uniforms.celestial_dir[2] or -1
u.celestial_dir.z = uniforms.celestial_dir[3] or 0
cx = uniforms.celestial_dir[1] or 0
cy = uniforms.celestial_dir[2] or -1
cz = uniforms.celestial_dir[3] or 0
else
u.celestial_dir.x = 0
u.celestial_dir.y = -1
u.celestial_dir.z = 0
cx, cy, cz = 0, -1, 0
end
local len = math.sqrt(cx * cx + cy * cy + cz * cz)
if len > 0.0001 then
local inv = 1.0 / len
cx, cy, cz = cx * inv, cy * inv, cz * inv
end
u.celestial_dir.x = cx
u.celestial_dir.y = cy
u.celestial_dir.z = cz
u.celestial_intensity = uniforms.celestial_intensity or 0.0
local lights_ptr = lights and lights._ptr or nil
@ -313,6 +319,14 @@ function gfx.create_vec3_array(count)
return ffi.new("pxl8_vec3[?]", count)
end
function gfx.set_wireframe(enabled)
C.pxl8_gfx_set_wireframe(core.gfx, enabled)
end
function gfx.get_wireframe()
return C.pxl8_gfx_get_wireframe(core.gfx)
end
local Material = {}
Material.__index = Material
@ -327,7 +341,6 @@ function Material.new(opts)
emissive = opts.emissive or false,
per_pixel = opts.per_pixel or false,
texture_id = opts.texture or 0xFFFFFFFF,
wireframe = opts.wireframe or false,
})
return setmetatable({ _ptr = mat }, Material)
end

View file

@ -117,10 +117,6 @@ function World:set_sim_distance(distance)
C.pxl8_world_set_sim_distance(self._ptr, distance)
end
function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled)
end
function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})

View file

@ -1,5 +1,50 @@
#include "pxl8_math.h"
f32 pxl8_fast_inv_sqrt(f32 x) {
#if defined(PXL8_NO_SIMD)
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1);
x = *(f32*)&i;
x = x * (1.5f - half * x * x);
return x;
#elif defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
v = _mm_rsqrt_ss(v);
return _mm_cvtss_f32(v);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrsqrte_f32(v);
est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est));
return vget_lane_f32(est, 0);
#else
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1);
x = *(f32*)&i;
x = x * (1.5f - half * x * x);
return x;
#endif
}
f32 pxl8_fast_rcp(f32 x) {
#if defined(PXL8_NO_SIMD)
return 1.0f / x;
#elif defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
__m128 rcp = _mm_rcp_ss(v);
rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp))));
return _mm_cvtss_f32(rcp);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrecpe_f32(v);
est = vmul_f32(est, vrecps_f32(v, est));
return vget_lane_f32(est, 0);
#else
return 1.0f / x;
#endif
}
u32 pxl8_hash32(u32 x) {
x ^= x >> 16;
x *= 0x85EBCA6Bu;
@ -18,10 +63,6 @@ u64 pxl8_hash64(u64 x) {
return x;
}
f32 pxl8_lerp_f32(f32 a, f32 b, f32 t) {
return a + (b - a) * t;
}
f32 pxl8_smoothstep(f32 t) {
return t * t * (3.0f - 2.0f * t);
}
@ -56,11 +97,11 @@ f32 pxl8_vec2_length(pxl8_vec2 v) {
}
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) {
f32 len = pxl8_vec2_length(v);
f32 len_sq = pxl8_vec2_dot(v, v);
if (len < 1e-6f) return (pxl8_vec2){0};
if (len_sq < 1e-12f) return (pxl8_vec2){0};
return pxl8_vec2_scale(v, 1.0f / len);
return pxl8_vec2_scale(v, pxl8_fast_inv_sqrt(len_sq));
}
pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) {
@ -112,11 +153,11 @@ pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) {
}
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) {
f32 len = pxl8_vec3_length(v);
f32 len_sq = pxl8_vec3_dot(v, v);
if (len < 1e-6f) return (pxl8_vec3){0};
if (len_sq < 1e-12f) return (pxl8_vec3){0};
return pxl8_vec3_scale(v, 1.0f / len);
return pxl8_vec3_scale(v, pxl8_fast_inv_sqrt(len_sq));
}
pxl8_mat4 pxl8_mat4_identity(void) {

View file

@ -15,50 +15,25 @@
#define PXL8_PI 3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f)
static inline f32 pxl8_fast_inv_sqrt(f32 x) {
#if defined(PXL8_NO_SIMD)
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1);
x = *(f32*)&i;
x = x * (1.5f - half * x * x);
return x;
#elif defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
v = _mm_rsqrt_ss(v);
return _mm_cvtss_f32(v);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrsqrte_f32(v);
est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est));
return vget_lane_f32(est, 0);
#else
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1);
x = *(f32*)&i;
x = x * (1.5f - half * x * x);
return x;
#endif
}
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
#define pxl8_clamp(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x)))
#define pxl8_clamp_byte(x) pxl8_clamp(x, 0, 255)
static inline f32 pxl8_fast_rcp(f32 x) {
#if defined(PXL8_NO_SIMD)
return 1.0f / x;
#elif defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
__m128 rcp = _mm_rcp_ss(v);
rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp))));
return _mm_cvtss_f32(rcp);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrecpe_f32(v);
est = vmul_f32(est, vrecps_f32(v, est));
return vget_lane_f32(est, 0);
#else
return 1.0f / x;
#endif
}
#define pxl8_abs(x) fabsf(x)
#define pxl8_ceil(x) ceilf(x)
#define pxl8_floor(x) floorf(x)
#define pxl8_sqrt(x) sqrtf(x)
#define pxl8_sin(x) sinf(x)
#define pxl8_cos(x) cosf(x)
#define pxl8_tan(x) tanf(x)
#define pxl8_exp(x) expf(x)
#define pxl8_log(x) logf(x)
#define pxl8_pow(x, y) powf(x, y)
#define pxl8_lerp(a, b, t) ((a) + ((b) - (a)) * (t))
f32 pxl8_fast_inv_sqrt(f32 x);
f32 pxl8_fast_rcp(f32 x);
typedef union pxl8_vec2 {
struct { f32 x, y; };
@ -66,15 +41,17 @@ typedef union pxl8_vec2 {
f32 v[2];
} pxl8_vec2;
typedef struct pxl8_vec3 {
f32 x, y, z;
typedef union pxl8_vec3 {
struct { f32 x, y, z; };
f32 v[3];
} pxl8_vec3;
typedef struct pxl8_vec4 {
f32 x, y, z, w;
typedef union pxl8_vec4 {
struct { f32 x, y, z, w; };
f32 v[4];
} pxl8_vec4;
typedef struct pxl8_mat4 {
typedef union pxl8_mat4 {
f32 m[16];
} pxl8_mat4;
@ -108,7 +85,6 @@ extern "C" {
u32 pxl8_hash32(u32 x);
u64 pxl8_hash64(u64 x);
f32 pxl8_lerp_f32(f32 a, f32 b, f32 t);
f32 pxl8_smoothstep(f32 t);
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);

View file

@ -24,9 +24,9 @@ f32 pxl8_value_noise(f32 x, f32 z, u64 seed) {
f32 c01 = pxl8_noise2d(x0, z1, seed);
f32 c11 = pxl8_noise2d(x1, z1, seed);
f32 a = pxl8_lerp_f32(c00, c10, tx);
f32 b = pxl8_lerp_f32(c01, c11, tx);
return pxl8_lerp_f32(a, b, tz);
f32 a = pxl8_lerp(c00, c10, tx);
f32 b = pxl8_lerp(c01, c11, tx);
return pxl8_lerp(a, b, tz);
}
f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) {
@ -50,15 +50,15 @@ f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) {
f32 c011 = pxl8_noise3d(x0, y1, z1, seed);
f32 c111 = pxl8_noise3d(x1, y1, z1, seed);
f32 a00 = pxl8_lerp_f32(c000, c100, tx);
f32 a10 = pxl8_lerp_f32(c010, c110, tx);
f32 a01 = pxl8_lerp_f32(c001, c101, tx);
f32 a11 = pxl8_lerp_f32(c011, c111, tx);
f32 a00 = pxl8_lerp(c000, c100, tx);
f32 a10 = pxl8_lerp(c010, c110, tx);
f32 a01 = pxl8_lerp(c001, c101, tx);
f32 a11 = pxl8_lerp(c011, c111, tx);
f32 b0 = pxl8_lerp_f32(a00, a10, ty);
f32 b1 = pxl8_lerp_f32(a01, a11, ty);
f32 b0 = pxl8_lerp(a00, a10, ty);
f32 b1 = pxl8_lerp(a01, a11, ty);
return pxl8_lerp_f32(b0, b1, tz);
return pxl8_lerp(b0, b1, tz);
}
f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves) {

View file

@ -44,6 +44,8 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);\n"
"void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);\n"
"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
"void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled);\n"
"bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx);\n"
"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_2d_clear(pxl8_gfx* ctx, u32 color);\n"
@ -272,7 +274,6 @@ static const char* pxl8_ffi_cdefs =
" bool dynamic_lighting;\n"
" bool emissive;\n"
" bool per_pixel;\n"
" bool wireframe;\n"
"} pxl8_gfx_material;\n"
"\n"
"typedef struct pxl8_vertex {\n"
@ -422,7 +423,6 @@ static const char* pxl8_ffi_cdefs =
"pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
"i32 pxl8_world_get_render_distance(const pxl8_world* world);\n"
"void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);\n"
"i32 pxl8_world_get_sim_distance(const pxl8_world* world);\n"

View file

@ -569,8 +569,3 @@ pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) {
void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) {
pxl8_free(state);
}
void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe) {
if (!state) return;
state->wireframe = wireframe;
}

View file

@ -9,12 +9,11 @@ extern "C" {
#endif
typedef struct pxl8_vxl_render_state {
bool wireframe;
u8 _unused;
} pxl8_vxl_render_state;
pxl8_vxl_render_state* pxl8_vxl_render_state_create(void);
void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state);
void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe);
typedef struct pxl8_vxl_mesh_config {
bool ambient_occlusion;

View file

@ -332,12 +332,10 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
bool wireframe = world->vxl_render_state && world->vxl_render_state->wireframe;
pxl8_gfx_material mat = {
.texture_id = 0,
.dynamic_lighting = true,
.alpha = 255,
.wireframe = wireframe,
};
i32 r = world->render_distance;
@ -426,18 +424,6 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_
pxl8_bsp_set_material(world->bsp_render_state, material_id, material);
}
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
if (!world) return;
ensure_bsp_render_state(world);
if (world->bsp_render_state) {
pxl8_bsp_set_wireframe(world->bsp_render_state, enabled);
}
if (world->vxl_render_state) {
pxl8_vxl_set_wireframe(world->vxl_render_state, enabled);
}
}
i32 pxl8_world_get_render_distance(const pxl8_world* world) {
if (!world) return 3;
return world->render_distance;

View file

@ -35,7 +35,6 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_sync(pxl8_world* world, pxl8_net* net);
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
i32 pxl8_world_get_render_distance(const pxl8_world* world);
void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);