myth_render 0.3.0

Core rendering system for the Myth engine (render graph, pipeline, GPU management).
Documentation
struct HiZTraceSegment {
    pixel_start: vec2<f32>,
    pixel_end: vec2<f32>,
    q0: vec3<f32>,
    q1: vec3<f32>,
    k0: f32,
    k1: f32,
};

struct HiZTraceConfig {
    screen_size: vec2<f32>,
    near_plane: f32,
    thickness: f32,
    mip_bias: f32,
    max_mip: u32,
    max_iterations: u32,
};

struct RaymarchResult {
    hit: bool,
    uv: vec2<f32>,
    view_pos: vec3<f32>,
    depth: f32,
    scene_depth: f32,
    iteration_count: u32,
};

fn hiz_trace_miss(iteration_count: u32) -> RaymarchResult {
    return RaymarchResult(false, vec2<f32>(0.0), vec3<f32>(0.0), 0.0, 0.0, iteration_count);
}

fn hiz_depth_to_linear(near_plane: f32, depth: f32) -> f32 {
    return near_plane / max(depth, 0.0001);
}

fn hiz_ray_pixel(segment: HiZTraceSegment, t: f32) -> vec2<f32> {
    return mix(segment.pixel_start, segment.pixel_end, t);
}

fn hiz_ray_view_position(segment: HiZTraceSegment, t: f32) -> vec3<f32> {
    let q = mix(segment.q0, segment.q1, t);
    let k = mix(segment.k0, segment.k1, t);
    let safe_k = max(abs(k), 1e-6) * select(-1.0, 1.0, k >= 0.0);
    return q / safe_k;
}

fn hiz_ray_linear_depth(segment: HiZTraceSegment, t: f32, near_plane: f32) -> f32 {
    return max(-hiz_ray_view_position(segment, t).z, near_plane);
}

fn hiz_scaled_thickness(config: HiZTraceConfig, depth_linear: f32) -> f32 {
    return config.thickness * max(depth_linear, 1.0);
}

fn hiz_boundary_epsilon_t(pixel_span: f32, cell_size: f32) -> f32 {
    let boundary_push_pixels = max(0.25, cell_size * 0.01);
    return boundary_push_pixels / max(pixel_span, 1.0);
}

fn hiz_next_boundary_t(
    current_pixel: vec2<f32>,
    current_t: f32,
    inv_dir_pixels: vec2<f32>,
    cell_size: f32,
) -> f32 {
    let cell = floor(current_pixel / cell_size);
    let next_boundary = vec2<f32>(
        (cell.x + select(0.0, 1.0, inv_dir_pixels.x > 0.0)) * cell_size,
        (cell.y + select(0.0, 1.0, inv_dir_pixels.y > 0.0)) * cell_size,
    );

    let tx = current_t + (next_boundary.x - current_pixel.x) * inv_dir_pixels.x;

    let ty = current_t + (next_boundary.y - current_pixel.y) * inv_dir_pixels.y;

    return min(tx, ty);
}

fn hiz_refine_hit(
    segment: HiZTraceSegment,
    config: HiZTraceConfig,
    depth_texture: texture_depth_2d,
    start_t: f32,
    end_t: f32,
    iteration_count: u32,
) -> RaymarchResult {
    let max_coord = max(config.screen_size - vec2<f32>(1.0), vec2<f32>(0.0));
    let max_coord_i = vec2<i32>(max_coord);

    var left = start_t;
    var right = end_t;
    var found = false;
    var hit_uv = vec2<f32>(0.0);
    var hit_view_pos = vec3<f32>(0.0);
    var hit_depth = 0.0;
    var hit_scene_depth = 0.0;

    for (var refine = 0u; refine < 5u; refine++) {
        let mid = 0.5 * (left + right);
        let pixel = clamp(hiz_ray_pixel(segment, mid), vec2<f32>(0.0), max_coord);
        let coord = clamp(vec2<i32>(pixel), vec2<i32>(0), max_coord_i);
        let scene_depth = textureLoad(depth_texture, coord, 0);
        let ray_depth = hiz_ray_linear_depth(segment, mid, config.near_plane);

        if (scene_depth <= 0.0) {
            left = mid;
            continue;
        }

        let scene_linear = hiz_depth_to_linear(config.near_plane, scene_depth);
        if (ray_depth < scene_linear) {
            left = mid;
        } else {
            right = mid;
            hit_uv = (vec2<f32>(coord) + vec2<f32>(0.5)) / config.screen_size;
            hit_view_pos = hiz_ray_view_position(segment, mid);
            hit_depth = ray_depth;
            hit_scene_depth = scene_depth;
            found = true;
        }
    }

    if (!found) {
        return hiz_trace_miss(iteration_count);
    }

    return RaymarchResult(true, hit_uv, hit_view_pos, hit_depth, hit_scene_depth, iteration_count);
}

fn trace_screen_space_ray_hiz(
    segment: HiZTraceSegment,
    config: HiZTraceConfig,
    hiz_texture: texture_2d<f32>,
    depth_texture: texture_depth_2d,
) -> RaymarchResult {
    let dir_pixels = segment.pixel_end - segment.pixel_start;
    let pixel_span = max(abs(dir_pixels.x), abs(dir_pixels.y));
    if (pixel_span < 1e-4) {
        return hiz_trace_miss(0u);
    }

    let span_safe = max(pixel_span, 1.0);
    let max_coord = max(config.screen_size - vec2<f32>(1.0), vec2<f32>(0.0));
    var current_mip = u32(clamp(
        floor(log2(span_safe)) + config.mip_bias,
        0.0,
        f32(config.max_mip),
    ));
    var current_t = hiz_boundary_epsilon_t(span_safe, exp2(f32(current_mip)));

    let inv_dir_pixels = select(vec2<f32>(1e30), 1.0 / dir_pixels, abs(dir_pixels) > vec2<f32>(1e-5));

    for (var iter = 0u; iter < config.max_iterations; iter++) {
        if (current_t >= 1.0) {
            return hiz_trace_miss(iter);
        }

        let pixel = hiz_ray_pixel(segment, current_t);
        if (pixel.x < 0.0 || pixel.x > max_coord.x || pixel.y < 0.0 || pixel.y > max_coord.y) {
            return hiz_trace_miss(iter);
        }

        // let cell_size = exp2(f32(current_mip));
        let cell_size = f32(1u << current_mip);
        // let mip_dims = vec2<i32>(textureDimensions(hiz_texture, i32(current_mip)));
        // let cell = clamp(
        //     vec2<i32>(floor(pixel / cell_size)),
        //     vec2<i32>(0),
        //     mip_dims - vec2<i32>(1),
        // );

        let cell = vec2<i32>(pixel) >> vec2<u32>(current_mip);

        let boundary_epsilon = hiz_boundary_epsilon_t(span_safe, cell_size);
        let boundary_t = min(
            hiz_next_boundary_t(pixel, current_t, inv_dir_pixels, cell_size) + boundary_epsilon,
            1.0,
        );
        let current_depth = hiz_ray_linear_depth(segment, current_t, config.near_plane);
        let boundary_depth = hiz_ray_linear_depth(segment, boundary_t, config.near_plane);
        let ray_min_depth = min(current_depth, boundary_depth);
        let ray_max_depth = max(current_depth, boundary_depth);

        let cell_depth = textureLoad(hiz_texture, cell, i32(current_mip)).r;
        let cell_linear = hiz_depth_to_linear(config.near_plane, cell_depth);
        let hit_candidate = cell_depth > 0.0 && ray_max_depth >= cell_linear;

        if (hit_candidate) {
            if (current_mip == 0u) {
                let thickness_limit = hiz_scaled_thickness(config, cell_linear);
                if (ray_min_depth > cell_linear + thickness_limit) {
                    current_t = boundary_t;
                    continue;
                }

                return hiz_refine_hit(
                    segment,
                    config,
                    depth_texture,
                    current_t,
                    boundary_t,
                    iter + 1u,
                );
            }

            current_mip -= 1u;
            continue;
        }

        current_t = boundary_t;
        current_mip = min(current_mip + 1u, config.max_mip);
    }

    return hiz_trace_miss(config.max_iterations);
}