#![allow(clippy::must_use_candidate, clippy::too_many_lines)]
pub mod camera;
pub mod decompress;
pub mod grid;
#[cfg(not(target_arch = "wasm32"))]
pub mod headless;
pub mod resident;
pub mod scene;
pub mod sprite_model;
mod lights;
mod overlay;
mod readback;
mod shader_src;
pub use camera::Camera;
pub use decompress::{decompress_chunk, ChunkUpload, BEDROCK_RGB, CHUNK_Z};
pub use grid::{bounding_box_of, GridUpload};
#[cfg(not(target_arch = "wasm32"))]
pub use headless::HeadlessGpu;
pub use resident::GpuChunkResident;
pub use scene::{
GpuSceneResident, GridRuntimeTransform, GridStaticMeta, RefreshOutcome, SceneUpload,
};
pub use sprite_model::{
build_sprite_model, build_sprite_model_with_materials, sprite_model_from_clip_frame,
sprite_model_from_clip_frame_with_materials, sprite_model_from_voxel_frame,
sprite_model_from_voxel_frame_with_materials, SpriteInstance, SpriteInstanceTransform,
SpriteModel, SpriteModelRegistry, SpriteRegistryResident,
};
pub use lights::{GpuLight, SceneLights, MAX_POINT_LIGHTS, MAX_SHADOW_CASTERS};
pub use overlay::{GpuImageQuad, GpuLine, GpuLineCamera};
pub use readback::pinhole_pixel_ray;
use std::sync::Arc;
use bytemuck::{Pod, Zeroable};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use roxlap_formats::color::Rgb;
use lights::{inject_grid_sun_dirs, pack_scene_lights, upload_grid_point_lights, GpuPointLight};
use overlay::{ImageResident, ImageResources, LineResources, LINE_NEAR_Z};
use shader_src::{scene_shader_source, sprite_shader_source};
#[derive(Debug, Clone, Copy)]
pub struct GpuRendererSettings {
pub power_preference: PowerPreference,
pub clear_colour: [f64; 3],
pub uncapped_present: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum PowerPreference {
Low,
High,
}
impl Default for GpuRendererSettings {
fn default() -> Self {
Self {
power_preference: PowerPreference::High,
clear_colour: [0.06, 0.08, 0.12],
uncapped_present: true,
}
}
}
#[derive(Debug)]
pub enum GpuInitError {
CreateSurface(wgpu::CreateSurfaceError),
NoAdapter,
RequestDevice(wgpu::RequestDeviceError),
}
impl std::fmt::Display for GpuInitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CreateSurface(e) => write!(f, "create_surface failed: {e}"),
Self::NoAdapter => write!(
f,
"no compatible adapter — does this system have a Vulkan/Metal/DX12 driver?"
),
Self::RequestDevice(e) => write!(f, "request_device failed: {e}"),
}
}
}
impl std::error::Error for GpuInitError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CreateSurface(e) => Some(e),
Self::RequestDevice(e) => Some(e),
Self::NoAdapter => None,
}
}
}
impl From<wgpu::CreateSurfaceError> for GpuInitError {
fn from(value: wgpu::CreateSurfaceError) -> Self {
Self::CreateSurface(value)
}
}
impl From<wgpu::RequestDeviceError> for GpuInitError {
fn from(value: wgpu::RequestDeviceError) -> Self {
Self::RequestDevice(value)
}
}
#[derive(Clone, Copy, Debug)]
pub struct PosterizeGpu {
pub levels: [u32; 3],
pub dither: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum RenderResolution {
#[default]
Native,
Fixed {
w: u32,
h: u32,
},
Scale(f32),
}
impl RenderResolution {
#[must_use]
fn logical_for(self, native: (u32, u32)) -> (u32, u32) {
let (nw, nh) = (native.0.max(1), native.1.max(1));
match self {
Self::Native => (nw, nh),
Self::Fixed { w, h } => (w.max(1), h.max(1)),
Self::Scale(f) => {
let s = f.max(1e-3);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let lw = ((nw as f32) * s).round() as u32;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let lh = ((nh as f32) * s).round() as u32;
(lw.max(1), lh.max(1))
}
}
}
}
#[allow(clippy::struct_excessive_bools)] pub struct GpuRenderer {
surface: wgpu::Surface<'static>,
surface_config: wgpu::SurfaceConfiguration,
device: wgpu::Device,
queue: wgpu::Queue,
adapter_info: String,
low_power: bool,
clear_colour: [f64; 3],
frame_count: u32,
flip_x: bool,
render_res: RenderResolution,
ssaa: u32,
posterize: Option<PosterizeGpu>,
scene_dda: Option<SceneDdaResources>,
scene_materials: Box<[MaterialGpu; 256]>,
scene_terrain_map: Vec<[u32; 2]>,
scene_terrain_translucent: bool,
dirty: FrameDirty,
sky_texture: wgpu::Texture,
sky_view: wgpu::TextureView,
sky_sampler: wgpu::Sampler,
fog_color: [f32; 3],
fog_near: f32,
fog_far: f32,
sprite_registry: Option<sprite_model::SpriteRegistryResident>,
sprite_model_dda: Option<SpriteModelDdaResources>,
sprite_materials: Box<[MaterialGpu; 256]>,
sprite_has_translucent: bool,
sprite_shadows_capable: bool,
sprite_lod_px: f32,
scene_mip_scan_dist: f32,
scene_side_shades: [[i32; 4]; 2],
scene_lights: SceneLights,
lights_sun_flags: u32,
lights_point_count: u32,
lights_packed_grids: u32,
last_fov_y_rad: f32,
pending_frame: Option<(wgpu::SurfaceTexture, wgpu::TextureView)>,
frame_pack: Option<FramePackBuffers>,
line_resources: Option<LineResources>,
line_vbuf: Option<wgpu::Buffer>,
line_vbuf_cap: u64,
line_bg_cache: Option<(wgpu::BindGroup, Option<wgpu::Buffer>)>,
image_resources: Option<ImageResources>,
image_vbuf: Option<wgpu::Buffer>,
image_vbuf_cap: u64,
image_bg_cache: std::collections::HashMap<usize, wgpu::BindGroup>,
image_bg_depth: Option<wgpu::Buffer>,
images: Vec<Option<ImageResident>>,
#[cfg(feature = "hud")]
egui_renderer: Option<egui_wgpu::Renderer>,
}
struct SceneDdaResources {
storage_size: (u32, u32),
logical_size: (u32, u32),
resolve_buf: wgpu::Buffer,
framebuffer: wgpu::Buffer,
uniform_buf: wgpu::Buffer,
bgl_dda: wgpu::BindGroupLayout,
pipeline_dda: wgpu::ComputePipeline,
pipeline_resolve: wgpu::ComputePipeline,
resolve_bg: wgpu::BindGroup,
resolve_dims: wgpu::Buffer,
blit_bg: wgpu::BindGroup,
blit_bg_direct: wgpu::BindGroup,
pipeline_blit: wgpu::RenderPipeline,
blit_dims: wgpu::Buffer,
depth_buffer: wgpu::Buffer,
depth_readback: wgpu::Buffer,
materials_pal_buf: wgpu::Buffer,
terrain_map_buf: wgpu::Buffer,
sprite_cast_dummy: Option<wgpu::Buffer>,
}
#[derive(Debug)]
pub(crate) struct FrameDirty {
pub(crate) scene_lights: bool,
pub(crate) sprite_lights: bool,
pub(crate) scene_depth_valid: bool,
}
impl Default for FrameDirty {
fn default() -> Self {
Self {
scene_lights: true,
sprite_lights: true,
scene_depth_valid: false,
}
}
}
impl FrameDirty {
pub(crate) fn mark_lights_changed(&mut self) {
self.scene_lights = true;
self.sprite_lights = true;
}
}
struct FramePackBuffers {
grid_cameras: wgpu::Buffer,
grid_cameras_cap: u64,
point_lights: wgpu::Buffer,
point_lights_cap: u64,
sprite_lights: wgpu::Buffer,
sprite_lights_cap: u64,
dda_bg: Option<CachedBindGroup>,
sprite_bg: Option<CachedBindGroup>,
}
struct CachedBindGroup {
bufs: Vec<(u32, wgpu::Buffer)>,
views: Vec<(u32, wgpu::TextureView)>,
bg: wgpu::BindGroup,
}
impl FramePackBuffers {
fn new(device: &wgpu::Device) -> Self {
let mk = |label: &str, cap: u64| {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: cap,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
};
let cam_cap = 4 * 144;
let light_cap = 4096;
Self {
grid_cameras: mk("roxlap-gpu scene_dda.grid_cameras", cam_cap),
grid_cameras_cap: cam_cap,
point_lights: mk("roxlap-gpu scene_dda.grid_point_lights", light_cap),
point_lights_cap: light_cap,
sprite_lights: mk("roxlap-gpu sprite_model_dda.point_lights", light_cap),
sprite_lights_cap: light_cap,
dda_bg: None,
sprite_bg: None,
}
}
fn write_grow(
device: &wgpu::Device,
queue: &wgpu::Queue,
buf: &mut wgpu::Buffer,
cap: &mut u64,
label: &str,
bytes: &[u8],
) {
let needed = bytes.len() as u64;
if needed > *cap {
let new_cap = needed.next_power_of_two();
*buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: new_cap,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
*cap = new_cap;
}
if !bytes.is_empty() {
queue.write_buffer(buf, 0, bytes);
}
}
fn write_cameras(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
cams: &[SceneDdaPerGridCamera],
) {
Self::write_grow(
device,
queue,
&mut self.grid_cameras,
&mut self.grid_cameras_cap,
"roxlap-gpu scene_dda.grid_cameras",
bytemuck::cast_slice(cams),
);
}
fn write_point_lights(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
lights: &[GpuPointLight],
) {
Self::write_grow(
device,
queue,
&mut self.point_lights,
&mut self.point_lights_cap,
"roxlap-gpu scene_dda.grid_point_lights",
bytemuck::cast_slice(lights),
);
}
fn write_sprite_lights(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
lights: &[GpuPointLight],
) {
Self::write_grow(
device,
queue,
&mut self.sprite_lights,
&mut self.sprite_lights_cap,
"roxlap-gpu sprite_model_dda.point_lights",
bytemuck::cast_slice(lights),
);
}
}
fn cached_bind_group<'a>(
slot: &'a mut Option<CachedBindGroup>,
device: &wgpu::Device,
label: &str,
layout: &wgpu::BindGroupLayout,
bufs: Vec<(u32, wgpu::Buffer)>,
views: Vec<(u32, wgpu::TextureView)>,
samplers: &[(u32, &wgpu::Sampler)],
) -> &'a wgpu::BindGroup {
let hit = slot
.as_ref()
.is_some_and(|c| c.bufs == bufs && c.views == views);
if !hit {
let mut entries: Vec<wgpu::BindGroupEntry> = bufs
.iter()
.map(|(binding, b)| wgpu::BindGroupEntry {
binding: *binding,
resource: b.as_entire_binding(),
})
.collect();
entries.extend(views.iter().map(|(binding, v)| wgpu::BindGroupEntry {
binding: *binding,
resource: wgpu::BindingResource::TextureView(v),
}));
entries.extend(samplers.iter().map(|&(binding, s)| wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::Sampler(s),
}));
entries.sort_by_key(|e| e.binding);
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &entries,
});
*slot = Some(CachedBindGroup { bufs, views, bg });
}
&slot.as_ref().expect("just cached").bg
}
struct SpriteModelDdaResources {
bgl: wgpu::BindGroupLayout,
pipeline: wgpu::ComputePipeline,
uniform_buf: wgpu::Buffer,
materials_buf: wgpu::Buffer,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct SpriteModelUniform {
cam_pos: [f32; 3],
_p0: f32,
cam_right: [f32; 3],
_p1: f32,
cam_down: [f32; 3],
_p2: f32,
cam_forward: [f32; 3],
_p3: f32,
fog_color: [f32; 4],
screen_size: [u32; 2],
instance_count: u32,
fog_far: f32,
fov_y_rad: f32,
tiles_x: u32,
tile_size: u32,
has_translucent: u32,
sun_dir: [f32; 4],
sun_color: [f32; 4],
ambient_color: [f32; 4],
sun_flags: u32,
point_light_count: u32,
_pad_dl: [u32; 2],
shadow_tint: [f32; 4],
style_bands: u32,
occ_num_pages: u32,
occ_page_words: u32,
grid_count: u32,
max_outer_steps: u32,
shadow_max_steps: u32,
shadow_bias: f32,
shadow_max_dist: f32,
shadow_strength: f32,
_pad_xs: [u32; 3],
}
const SPRITE_TILE_SIZE: u32 = 16;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct MaterialGpu {
alpha: f32,
mode: u32,
}
fn material_palette(
table: &roxlap_formats::material::MaterialTable,
) -> (Box<[MaterialGpu; 256]>, bool) {
let mut out = Box::new(
[MaterialGpu {
alpha: 1.0,
mode: 0,
}; 256],
);
let mut any_translucent = false;
for (id, slot) in out.iter_mut().enumerate() {
let m = table.get(id as u8);
slot.alpha = f32::from(m.alpha) / 255.0;
slot.mode = u32::from(m.mode.as_u8());
if !m.is_opaque() {
any_translucent = true;
}
}
(out, any_translucent)
}
fn upload_grid_cameras(device: &wgpu::Device, cams: &[SceneDdaPerGridCamera]) -> wgpu::Buffer {
use wgpu::util::DeviceExt;
let one = [SceneDdaPerGridCamera::zeroed()];
let src: &[SceneDdaPerGridCamera] = if cams.is_empty() { &one } else { cams };
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu scene_dda.grid_cameras"),
contents: bytemuck::cast_slice(src),
usage: wgpu::BufferUsages::STORAGE,
})
}
const _: () = assert!(scene::MAX_OCC_PAGES == 4);
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct SceneDdaPerGridCamera {
pos: [f32; 3],
_pad0: f32,
right: [f32; 3],
_pad1: f32,
down: [f32; 3],
_pad2: f32,
forward: [f32; 3],
_pad3: f32,
sun_dir: [f32; 4],
world_origin: [f32; 4],
rot0: [f32; 4],
rot1: [f32; 4],
rot2: [f32; 4],
}
impl SceneDdaPerGridCamera {
fn from_camera(c: &Camera) -> Self {
Self {
pos: c.position,
_pad0: 0.0,
right: c.right,
_pad1: 0.0,
down: c.down,
_pad2: 0.0,
forward: c.forward,
_pad3: 0.0,
sun_dir: [0.0; 4],
world_origin: [0.0; 4],
rot0: [1.0, 0.0, 0.0, 0.0],
rot1: [0.0, 1.0, 0.0, 0.0],
rot2: [0.0, 0.0, 1.0, 0.0],
}
}
fn set_world_transform(&mut self, t: &GridWorldTransform) {
self.world_origin = [t.origin[0], t.origin[1], t.origin[2], 0.0];
self.rot0 = [t.rot_cols[0][0], t.rot_cols[0][1], t.rot_cols[0][2], 0.0];
self.rot1 = [t.rot_cols[1][0], t.rot_cols[1][1], t.rot_cols[1][2], 0.0];
self.rot2 = [t.rot_cols[2][0], t.rot_cols[2][1], t.rot_cols[2][2], 0.0];
}
}
#[derive(Clone, Copy)]
pub struct GridWorldTransform {
pub origin: [f32; 3],
pub rot_cols: [[f32; 3]; 3],
}
impl Default for GridWorldTransform {
fn default() -> Self {
Self {
origin: [0.0; 3],
rot_cols: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct SceneDdaUniform {
fov_y_rad: f32,
grid_count: u32,
max_outer_steps: u32,
_pad0: u32,
screen_size: [u32; 2],
_pad1: [u32; 2],
fog_color: [f32; 4],
fog_far: f32,
write_depth: u32,
occ_page_words: u32,
occ_num_pages: u32,
mip_scan_dist: f32,
terrain_has_translucent: u32,
terrain_map_count: u32,
_pad4: u32,
sky_cam: SceneDdaPerGridCamera,
side_shades0: [i32; 4],
side_shades1: [i32; 4],
sun_color: [f32; 4],
ambient_color: [f32; 4],
sun_flags: u32,
point_light_count: u32,
shadow_max_steps: u32,
_pad5: u32,
shadow_bias: f32,
shadow_max_dist: f32,
_pad6: [f32; 2],
shadow_tint: [f32; 4],
style_bands: u32,
sprite_cast_count: u32,
_pad7: [u32; 2],
}
impl GpuRenderer {
pub async fn new<W>(
window: Arc<W>,
size: (u32, u32),
settings: GpuRendererSettings,
) -> Result<Self, GpuInitError>
where
W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
{
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let surface = instance.create_surface(window.clone())?;
let adapter = Self::request_adapter(&instance, Some(&surface), settings).await?;
let (device, queue) = Self::request_device(&adapter).await?;
Ok(Self::finish_init(
&adapter, device, queue, surface, size, settings,
))
}
#[cfg(target_arch = "wasm32")]
pub async fn new_from_canvas(
canvas: web_sys::HtmlCanvasElement,
size: (u32, u32),
settings: GpuRendererSettings,
) -> Result<Self, GpuInitError> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let adapter = Self::request_adapter(&instance, None, settings).await?;
let (device, queue) = Self::request_device(&adapter).await?;
let surface = instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas))?;
Ok(Self::finish_init(
&adapter, device, queue, surface, size, settings,
))
}
async fn request_adapter(
instance: &wgpu::Instance,
compatible_surface: Option<&wgpu::Surface<'static>>,
settings: GpuRendererSettings,
) -> Result<wgpu::Adapter, GpuInitError> {
let power_preference = match settings.power_preference {
PowerPreference::Low => wgpu::PowerPreference::LowPower,
PowerPreference::High => wgpu::PowerPreference::HighPerformance,
};
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface,
force_fallback_adapter: false,
})
.await
.map_err(|_| GpuInitError::NoAdapter)
}
async fn request_device(
adapter: &wgpu::Adapter,
) -> Result<(wgpu::Device, wgpu::Queue), GpuInitError> {
Ok(adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("roxlap-gpu device"),
required_features: wgpu::Features::empty(),
required_limits: pick_required_limits(&adapter.limits()),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
})
.await?)
}
fn finish_init(
adapter: &wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>,
size: (u32, u32),
settings: GpuRendererSettings,
) -> Self {
let info = adapter.get_info();
let adapter_info = format!(
"{name} ({backend:?}, {device_type:?})",
name = info.name,
backend = info.backend,
device_type = info.device_type,
);
let low_power = info.device_type != wgpu::DeviceType::DiscreteGpu;
let caps = surface.get_capabilities(adapter);
let surface_format = caps
.formats
.iter()
.copied()
.find(|f| {
matches!(
f,
wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm
)
})
.or_else(|| caps.formats.iter().copied().find(|f| !f.is_srgb()))
.unwrap_or(caps.formats[0]);
let present_mode = if settings.uncapped_present {
pick_present_mode(&caps.present_modes)
} else {
wgpu::PresentMode::Fifo
};
eprintln!(
"roxlap-gpu: present mode = {present_mode:?} (available: {:?})",
caps.present_modes,
);
let (init_w, init_h) = size;
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: init_w.max(1),
height: init_h.max(1),
present_mode,
alpha_mode: caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
let default_sky_pixel = [0x80u8, 0x80, 0x80, 0xff];
let (sky_texture, sky_view) = create_sky_texture(&device, 1, 1, &default_sky_pixel);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &sky_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&default_sky_pixel,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("roxlap-gpu sky_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let sprite_shadows_capable = device.limits().max_storage_buffers_per_shader_stage
>= SPRITE_SHADOW_MIN_STORAGE_BUFFERS;
Self {
surface,
surface_config,
device,
queue,
adapter_info,
low_power,
clear_colour: settings.clear_colour,
frame_count: 0,
flip_x: false,
render_res: RenderResolution::Native,
ssaa: 1,
posterize: None,
scene_dda: None,
scene_materials: Box::new(
[MaterialGpu {
alpha: 1.0,
mode: 0,
}; 256],
),
scene_terrain_map: Vec::new(),
scene_terrain_translucent: false,
dirty: FrameDirty::default(),
sky_texture,
sky_view,
sky_sampler,
fog_color: [0.66, 0.74, 0.88],
fog_near: 0.0,
fog_far: 1.0e30,
sprite_registry: None,
sprite_model_dda: None,
sprite_shadows_capable,
sprite_materials: Box::new(
[MaterialGpu {
alpha: 1.0,
mode: 0,
}; 256],
),
sprite_has_translucent: false,
sprite_lod_px: 1.0,
scene_mip_scan_dist: 64.0,
scene_side_shades: [[0; 4]; 2],
scene_lights: SceneLights::default(),
lights_sun_flags: 0,
lights_point_count: 0,
lights_packed_grids: 0,
last_fov_y_rad: 0.0,
pending_frame: None,
frame_pack: None,
line_resources: None,
line_vbuf: None,
line_vbuf_cap: 0,
line_bg_cache: None,
image_resources: None,
image_vbuf: None,
image_vbuf_cap: 0,
image_bg_cache: std::collections::HashMap::new(),
image_bg_depth: None,
images: Vec::new(),
#[cfg(feature = "hud")]
egui_renderer: None,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_blocking<W>(
window: Arc<W>,
size: (u32, u32),
settings: GpuRendererSettings,
) -> Result<Self, GpuInitError>
where
W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
{
pollster::block_on(Self::new(window, size, settings))
}
pub fn adapter_info(&self) -> &str {
&self.adapter_info
}
pub fn low_power(&self) -> bool {
self.low_power
}
pub fn device(&self) -> &wgpu::Device {
&self.device
}
#[must_use]
pub fn sprite_shadows_capable(&self) -> bool {
self.sprite_shadows_capable
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub fn set_flip_x(&mut self, flip: bool) {
self.flip_x = flip;
}
pub fn set_sky_panorama(&mut self, rgba: &[u8], width: u32, height: u32) {
assert_eq!(
rgba.len(),
(width as usize) * (height as usize) * 4,
"set_sky_panorama: expected w*h*4 bytes, got {}",
rgba.len(),
);
let (tex, view) = create_sky_texture(&self.device, width, height, rgba);
self.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
self.sky_texture = tex;
self.sky_view = view;
}
pub fn set_fog(&mut self, color: [f32; 3], near: f32, far: f32) {
self.fog_color = color;
self.fog_near = near;
self.fog_far = far.max(near + 1.0);
}
pub fn resize(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
self.scene_dda = None;
}
pub fn set_render_resolution(&mut self, res: RenderResolution) {
self.render_res = res;
}
pub fn set_ssaa(&mut self, factor: u8) {
self.ssaa = u32::from(factor).clamp(1, 4);
}
pub fn set_posterize(&mut self, cfg: Option<PosterizeGpu>) {
self.posterize = cfg;
}
#[must_use]
pub fn logical_dims(&self) -> (u32, u32) {
self.render_res.logical_for(self.surface_dims())
}
#[must_use]
pub fn render_dims(&self) -> (u32, u32) {
let (lw, lh) = self.logical_dims();
(lw * self.ssaa, lh * self.ssaa)
}
#[must_use]
pub fn surface_dims(&self) -> (u32, u32) {
(self.surface_config.width, self.surface_config.height)
}
fn acquire_frame(&self) -> Option<wgpu::SurfaceTexture> {
use wgpu::CurrentSurfaceTexture as C;
match self.surface.get_current_texture() {
C::Success(t) | C::Suboptimal(t) => Some(t),
C::Outdated | C::Lost => {
self.surface.configure(&self.device, &self.surface_config);
None
}
C::Timeout | C::Occluded | C::Validation => None,
}
}
pub fn render(&mut self) {
let Some(surf_tex) = self.acquire_frame() else {
return;
};
let view = surf_tex
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let phase = f64::from(self.frame_count % 1257) * 0.005;
let [r, g, b] = self.clear_colour;
let drift = (phase.sin() * 0.04 + 0.04).clamp(0.0, 0.1);
let clear = wgpu::Color {
r: (r + drift).clamp(0.0, 1.0),
g: (g + drift * 0.5).clamp(0.0, 1.0),
b: (b + drift * 0.25).clamp(0.0, 1.0),
a: 1.0,
};
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("roxlap-gpu encoder"),
});
{
let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("roxlap-gpu clear"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(clear),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
self.queue.submit(std::iter::once(encoder.finish()));
surf_tex.present();
self.frame_count = self.frame_count.wrapping_add(1);
}
pub fn render_scene(
&mut self,
scene: &GpuSceneResident,
cameras: &[Camera],
grid_world: &[GridWorldTransform],
sprite_camera: &Camera,
fov_y_rad: f32,
max_outer_steps: u32,
) {
assert_eq!(
cameras.len(),
scene.grid_count as usize,
"render_scene: {} cameras supplied, scene has {} grids",
cameras.len(),
scene.grid_count,
);
self.last_fov_y_rad = fov_y_rad;
self.pending_frame = None;
let Some(surf_tex) = self.acquire_frame() else {
return;
};
let surf_view = surf_tex
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let surface_w = self.surface_config.width;
let surface_h = self.surface_config.height;
let surface_format = self.surface_config.format;
let (logical_w, logical_h) = self.logical_dims();
let (render_w, render_h) = self.render_dims();
let needs_build = match &self.scene_dda {
Some(r) => {
r.storage_size != (render_w, render_h) || r.logical_size != (logical_w, logical_h)
}
None => true,
};
if needs_build {
self.scene_dda = Some(self.build_scene_dda(
render_w,
render_h,
logical_w,
logical_h,
surface_w,
surface_h,
surface_format,
));
}
if self.sprite_registry.is_some() && self.sprite_model_dda.is_none() {
self.sprite_model_dda = Some(self.build_sprite_model_dda());
}
let sprite_pass: Option<(u32, u32)> = if let Some(reg) = self.sprite_registry.as_mut() {
if reg.instance_capacity > 0 {
let cam = sprite_camera;
#[allow(clippy::cast_precision_loss)]
let aspect = render_w as f32 / render_h as f32;
let half_h = (fov_y_rad * 0.5).tan();
let frustum = sprite_model::ViewFrustum {
pos: cam.position,
right: cam.right,
down: cam.down,
forward: cam.forward,
half_w: half_h * aspect,
half_h,
far: 1.0e9,
};
let (visible, tiles_x, _tiles_y) = reg.cull_bin_upload(
&self.device,
&self.queue,
&frustum,
render_w,
render_h,
SPRITE_TILE_SIZE,
self.sprite_lod_px,
);
(visible > 0).then_some((visible, tiles_x))
} else {
None
}
} else {
None
};
let dda = self.scene_dda.as_ref().expect("just built");
self.queue.write_buffer(
&dda.blit_dims,
16,
bytemuck::bytes_of(&[u32::from(self.flip_x), 0u32]),
);
let (plevels, pdither) = match self.posterize {
Some(p) => (p.levels, p.dither),
None => ([1u32; 3], 0u32),
};
self.queue.write_buffer(
&dda.resolve_dims,
20,
bytemuck::bytes_of(&[plevels[0], plevels[1], plevels[2], pdither]),
);
let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
.iter()
.map(SceneDdaPerGridCamera::from_camera)
.collect();
for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
c.set_world_transform(t);
}
if self.frame_pack.is_none() {
self.frame_pack = Some(FramePackBuffers::new(&self.device));
}
let lights = &self.scene_lights;
inject_grid_sun_dirs(lights, &mut cam_vec);
let fp = self.frame_pack.as_mut().expect("just built");
fp.write_cameras(&self.device, &self.queue, &cam_vec);
if self.dirty.scene_lights || self.lights_packed_grids != scene.grid_count {
let (packed_lights, sun_flags, point_count) =
pack_scene_lights(lights, scene.grid_count as usize);
fp.write_point_lights(&self.device, &self.queue, &packed_lights);
self.lights_sun_flags = sun_flags;
self.lights_point_count = point_count;
self.lights_packed_grids = scene.grid_count;
self.dirty.scene_lights = false;
}
let (sun_flags, point_count) = (self.lights_sun_flags, self.lights_point_count);
let uniform = SceneDdaUniform {
fov_y_rad,
grid_count: scene.grid_count,
max_outer_steps,
_pad0: 0,
screen_size: [render_w, render_h],
_pad1: [0; 2],
fog_color: [
self.fog_color[0],
self.fog_color[1],
self.fog_color[2],
self.fog_near,
],
fog_far: self.fog_far,
write_depth: 1,
occ_page_words: scene.occupancy_page_words,
occ_num_pages: scene.occupancy_num_pages,
mip_scan_dist: self.scene_mip_scan_dist,
terrain_has_translucent: u32::from(self.scene_terrain_translucent),
terrain_map_count: self.scene_terrain_map.len() as u32,
_pad4: 0,
sky_cam: SceneDdaPerGridCamera::from_camera(sprite_camera),
side_shades0: self.scene_side_shades[0],
side_shades1: self.scene_side_shades[1],
sun_color: [
lights.sun_color[0],
lights.sun_color[1],
lights.sun_color[2],
lights.sun_intensity,
],
ambient_color: [
lights.ambient[0],
lights.ambient[1],
lights.ambient[2],
lights.shadow_strength,
],
sun_flags,
point_light_count: point_count,
shadow_max_steps: lights.shadow_max_steps,
_pad5: 0,
shadow_bias: lights.shadow_bias,
shadow_max_dist: lights.shadow_max_dist,
_pad6: [0.0; 2],
shadow_tint: [
lights.shadow_tint[0],
lights.shadow_tint[1],
lights.shadow_tint[2],
0.0,
],
style_bands: lights.style_bands,
sprite_cast_count: if self.sprite_shadows_capable {
sprite_pass.map_or(0, |(visible, _)| visible)
} else {
0
},
_pad7: [0; 2],
};
self.queue
.write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
let mut dda_bufs: Vec<(u32, wgpu::Buffer)> = vec![
(0, dda.uniform_buf.clone()),
(1, scene.occupancy_pages[0].clone()),
(2, scene.all_color_offsets.clone()),
(3, scene.all_colors.clone()),
(4, scene.all_chunk_colors_base.clone()),
(5, scene.all_chunk_occupancy.clone()),
(6, scene.grid_static_meta.clone()),
(7, scene.all_slot_chunk_idx.clone()),
(8, dda.framebuffer.clone()),
(11, dda.depth_buffer.clone()),
(12, scene.occupancy_pages[1].clone()),
(13, scene.occupancy_pages[2].clone()),
(14, scene.occupancy_pages[3].clone()),
(15, fp.grid_cameras.clone()),
(16, dda.materials_pal_buf.clone()),
(17, dda.terrain_map_buf.clone()),
(18, fp.point_lights.clone()),
];
if self.sprite_shadows_capable {
let dummy = dda
.sprite_cast_dummy
.as_ref()
.expect("capable scene_dda has a sprite-cast dummy");
let (insts, models, occ) = match &self.sprite_registry {
Some(reg) => (®.instances, ®.model_meta, ®.occupancy),
None => (dummy, dummy, dummy),
};
dda_bufs.push((19, insts.clone()));
dda_bufs.push((20, models.clone()));
dda_bufs.push((21, occ.clone()));
}
let dda_bg = cached_bind_group(
&mut fp.dda_bg,
&self.device,
"roxlap-gpu scene_dda.bg",
&dda.bgl_dda,
dda_bufs,
vec![(9, self.sky_view.clone())],
&[(10, &self.sky_sampler)],
)
.clone();
let sprite_model_bg = match (&self.sprite_model_dda, &self.sprite_registry, sprite_pass) {
(Some(smd), Some(reg), Some((visible, tiles_x))) => {
let cam = sprite_camera;
let dl = &self.scene_lights;
let sprite_sun_enabled = dl.world_sun_dir != [0.0; 3];
let sprite_point_count = dl.world_points.len().min(MAX_POINT_LIGHTS) as u32;
if self.dirty.sprite_lights {
let sprite_pts: Vec<GpuPointLight> = dl
.world_points
.iter()
.take(MAX_POINT_LIGHTS)
.map(|l| GpuPointLight {
pos: l.position,
radius: l.radius,
color: l.color,
intensity: l.intensity,
spot_dir: l.spot_dir,
cos_outer: l.cos_outer,
cos_inner: l.cos_inner,
casts_shadow: u32::from(l.casts_shadow),
_pad: [0; 2],
})
.collect();
fp.write_sprite_lights(&self.device, &self.queue, &sprite_pts);
self.dirty.sprite_lights = false;
}
let sprite_sun_flags = u32::from(sprite_sun_enabled)
| (u32::from(dl.sun_casts_shadow) << 1)
| (u32::from(dl.enabled) << 2);
let uni = SpriteModelUniform {
cam_pos: cam.position,
_p0: 0.0,
cam_right: cam.right,
_p1: 0.0,
cam_down: cam.down,
_p2: 0.0,
cam_forward: cam.forward,
_p3: 0.0,
fog_color: [
self.fog_color[0],
self.fog_color[1],
self.fog_color[2],
self.fog_near,
],
screen_size: [render_w, render_h],
instance_count: visible,
fog_far: self.fog_far,
fov_y_rad,
tiles_x,
tile_size: SPRITE_TILE_SIZE,
has_translucent: u32::from(self.sprite_has_translucent),
sun_dir: [
dl.world_sun_dir[0],
dl.world_sun_dir[1],
dl.world_sun_dir[2],
0.0,
],
sun_color: [
dl.sun_color[0],
dl.sun_color[1],
dl.sun_color[2],
dl.sun_intensity,
],
ambient_color: [dl.ambient[0], dl.ambient[1], dl.ambient[2], 0.0],
sun_flags: sprite_sun_flags,
point_light_count: sprite_point_count,
_pad_dl: [0; 2],
shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
style_bands: dl.style_bands,
occ_num_pages: scene.occupancy_num_pages,
occ_page_words: scene.occupancy_page_words,
grid_count: scene.grid_count,
max_outer_steps,
shadow_max_steps: dl.shadow_max_steps,
shadow_bias: dl.shadow_bias,
shadow_max_dist: dl.shadow_max_dist,
shadow_strength: dl.shadow_strength,
_pad_xs: [0; 3],
};
self.queue
.write_buffer(&smd.uniform_buf, 0, bytemuck::bytes_of(&uni));
let mut sprite_bufs: Vec<(u32, wgpu::Buffer)> = vec![
(0, smd.uniform_buf.clone()),
(1, reg.occupancy.clone()),
(2, reg.colors.clone()),
(3, reg.color_offsets.clone()),
(4, reg.model_meta.clone()),
(5, reg.instances.clone()),
(6, dda.depth_buffer.clone()),
(7, dda.framebuffer.clone()),
(8, reg.tile_ranges.clone()),
(9, reg.tile_instances.clone()),
(10, reg.dirs.clone()),
(11, reg.colmul.clone()),
(12, smd.materials_buf.clone()),
(13, reg.materials_vox.clone()),
(15, fp.sprite_lights.clone()),
];
if self.sprite_shadows_capable {
let terrain: [(u32, &wgpu::Buffer); 8] = [
(16, &scene.occupancy_pages[0]),
(17, &scene.occupancy_pages[1]),
(18, &scene.occupancy_pages[2]),
(19, &scene.occupancy_pages[3]),
(20, &scene.all_chunk_occupancy),
(21, &scene.all_slot_chunk_idx),
(22, &scene.grid_static_meta),
(23, &fp.grid_cameras),
];
for (binding, buf) in terrain {
sprite_bufs.push((binding, buf.clone()));
}
}
Some(
cached_bind_group(
&mut fp.sprite_bg,
&self.device,
"roxlap-gpu sprite_model_dda.bg",
&smd.bgl,
sprite_bufs,
Vec::new(),
&[],
)
.clone(),
)
}
_ => None,
};
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("roxlap-gpu scene encoder"),
});
{
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("roxlap-gpu scene_dda compute"),
timestamp_writes: None,
});
cpass.set_pipeline(&dda.pipeline_dda);
cpass.set_bind_group(0, &dda_bg, &[]);
cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
}
if let (Some(smd), Some(bg)) = (&self.sprite_model_dda, &sprite_model_bg) {
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("roxlap-gpu sprite_model_dda"),
timestamp_writes: None,
});
cpass.set_pipeline(&smd.pipeline);
cpass.set_bind_group(0, bg, &[]);
cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
}
let identity_resolve =
(render_w, render_h) == (logical_w, logical_h) && self.posterize.is_none();
if !identity_resolve {
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("roxlap-gpu scene_dda resolve"),
timestamp_writes: None,
});
cpass.set_pipeline(&dda.pipeline_resolve);
cpass.set_bind_group(0, &dda.resolve_bg, &[]);
cpass.dispatch_workgroups(logical_w.div_ceil(8), logical_h.div_ceil(8), 1);
}
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("roxlap-gpu scene_dda blit"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &surf_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
rpass.set_pipeline(&dda.pipeline_blit);
rpass.set_bind_group(
0,
if identity_resolve {
&dda.blit_bg_direct
} else {
&dda.blit_bg
},
&[],
);
rpass.draw(0..3, 0..1);
}
self.queue.submit(std::iter::once(encoder.finish()));
self.dirty.scene_depth_valid = true;
self.pending_frame = Some((surf_tex, surf_view));
self.frame_count = self.frame_count.wrapping_add(1);
}
pub fn render_clear_deferred(&mut self) {
self.dirty.scene_depth_valid = false;
self.pending_frame = None;
let Some(surf_tex) = self.acquire_frame() else {
return;
};
let view = surf_tex
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let [r, g, b] = self.clear_colour;
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("roxlap-gpu clear (deferred)"),
});
{
let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("roxlap-gpu clear (deferred)"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color { r, g, b, a: 1.0 }),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
self.queue.submit(std::iter::once(encoder.finish()));
self.pending_frame = Some((surf_tex, view));
}
pub fn present(&mut self) {
if let Some((surf_tex, _view)) = self.pending_frame.take() {
surf_tex.present();
}
}
pub fn wait_idle(&mut self) {
self.pending_frame = None;
#[cfg(not(target_arch = "wasm32"))]
{
self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
}
}
#[must_use]
pub fn project_point(
&self,
cam_pos: [f32; 3],
right: [f32; 3],
down: [f32; 3],
forward: [f32; 3],
world: [f32; 3],
) -> Option<(f32, f32)> {
let dda = self.scene_dda.as_ref()?;
let (w, h) = dda.storage_size;
if w == 0 || h == 0 || self.last_fov_y_rad <= 0.0 {
return None;
}
let d = [
world[0] - cam_pos[0],
world[1] - cam_pos[1],
world[2] - cam_pos[2],
];
let cz = forward[0] * d[0] + forward[1] * d[1] + forward[2] * d[2];
if cz < LINE_NEAR_Z {
return None;
}
let cx = right[0] * d[0] + right[1] * d[1] + right[2] * d[2];
let cy = down[0] * d[0] + down[1] * d[1] + down[2] * d[2];
let half_h = (self.last_fov_y_rad * 0.5).tan();
let half_w = half_h * (w as f32 / h as f32);
let ndc_x = (cx / cz) / half_w;
let ndc_y = -(cy / cz) / half_h;
let sx = (ndc_x * 0.5 + 0.5) * w as f32;
let sy = (0.5 - ndc_y * 0.5) * h as f32;
Some((sx, sy))
}
fn build_scene_dda(
&self,
width: u32,
height: u32,
logical_w: u32,
logical_h: u32,
surface_w: u32,
surface_h: u32,
surface_format: wgpu::TextureFormat,
) -> SceneDdaResources {
let framebuffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.framebuffer"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let resolve_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_buf"),
size: u64::from(logical_w) * u64::from(logical_h) * 4,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let resolve_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_dims"),
size: 48,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.queue.write_buffer(
&resolve_dims,
0,
bytemuck::bytes_of(&[width, height, logical_w, logical_h, self.ssaa]),
);
let blit_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.blit_dims"),
size: 32,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.queue.write_buffer(
&blit_dims,
0,
bytemuck::bytes_of(&[
logical_w,
logical_h,
surface_w,
surface_h,
u32::from(self.flip_x),
0u32,
0u32,
0u32,
]),
);
let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.uniform"),
size: std::mem::size_of::<SceneDdaUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let depth_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.depth"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let depth_readback = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.depth_readback"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let capable = self.sprite_shadows_capable;
let dda_shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene_dda.wgsl"),
source: wgpu::ShaderSource::Wgsl(scene_shader_source(capable).into()),
});
let mut dda_entries = vec![
bgl_uniform_entry(0),
bgl_storage_entry(1, true),
bgl_storage_entry(2, true),
bgl_storage_entry(3, true),
bgl_storage_entry(4, true),
bgl_storage_entry(5, true),
bgl_storage_entry(6, true),
bgl_storage_entry(7, true),
bgl_storage_entry(8, false),
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
bgl_storage_entry(11, false),
bgl_storage_entry(12, true),
bgl_storage_entry(13, true),
bgl_storage_entry(14, true),
bgl_storage_entry(15, true),
bgl_storage_entry(16, true),
bgl_storage_entry(17, true),
bgl_storage_entry(18, true),
];
if capable {
dda_entries.push(bgl_storage_entry(19, true)); dda_entries.push(bgl_storage_entry(20, true)); dda_entries.push(bgl_storage_entry(21, true)); }
let bgl_dda = self
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.bgl"),
entries: &dda_entries,
});
let dda_pl = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.layout"),
bind_group_layouts: &[Some(&bgl_dda)],
immediate_size: 0,
});
let pipeline_dda = self
.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("roxlap-gpu scene_dda.pipeline"),
layout: Some(&dda_pl),
module: &dda_shader,
entry_point: Some("render_scene"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let resolve_shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene_resolve.wgsl"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/scene_resolve.wgsl").into(),
),
});
let bgl_resolve = self
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_bgl"),
entries: &[
bgl_storage_entry(0, true), bgl_storage_entry(1, false), bgl_uniform_entry(2), ],
});
let resolve_pl = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_layout"),
bind_group_layouts: &[Some(&bgl_resolve)],
immediate_size: 0,
});
let pipeline_resolve =
self.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_pipeline"),
layout: Some(&resolve_pl),
module: &resolve_shader,
entry_point: Some("main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let resolve_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("roxlap-gpu scene_dda.resolve_bg"),
layout: &bgl_resolve,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: framebuffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: resolve_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: resolve_dims.as_entire_binding(),
},
],
});
let blit_shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene_blit.wgsl"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_blit.wgsl").into()),
});
let bgl_blit = self
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.blit_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let blit_pl = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("roxlap-gpu scene_dda.blit_layout"),
bind_group_layouts: &[Some(&bgl_blit)],
immediate_size: 0,
});
let pipeline_blit = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("roxlap-gpu scene_dda.blit_pipeline"),
layout: Some(&blit_pl),
vertex: wgpu::VertexState {
module: &blit_shader,
entry_point: Some("vs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &blit_shader,
entry_point: Some("fs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("roxlap-gpu scene_dda.blit_bg"),
layout: &bgl_blit,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: resolve_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: blit_dims.as_entire_binding(),
},
],
});
let blit_bg_direct = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("roxlap-gpu scene_dda.blit_bg_direct"),
layout: &bgl_blit,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: framebuffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: blit_dims.as_entire_binding(),
},
],
});
let (materials_pal_buf, terrain_map_buf) = {
use wgpu::util::DeviceExt;
let pal = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu scene_dda.materials_pal"),
contents: bytemuck::cast_slice(self.scene_materials.as_slice()),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let mut rows = [[0u32; 2]; 256];
for (slot, &row) in rows.iter_mut().zip(self.scene_terrain_map.iter()) {
*slot = row;
}
let map = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu scene_dda.terrain_map"),
contents: bytemuck::cast_slice(&rows),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
(pal, map)
};
SceneDdaResources {
storage_size: (width, height),
logical_size: (logical_w, logical_h),
framebuffer,
resolve_buf,
uniform_buf,
bgl_dda,
pipeline_dda,
pipeline_resolve,
resolve_bg,
resolve_dims,
blit_bg,
blit_bg_direct,
pipeline_blit,
blit_dims,
depth_buffer,
depth_readback,
materials_pal_buf,
terrain_map_buf,
sprite_cast_dummy: capable.then(|| {
self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu scene_dda.sprite_cast_dummy"),
size: 80,
usage: wgpu::BufferUsages::STORAGE,
mapped_at_creation: false,
})
}),
}
}
pub fn set_sprite_instances(
&mut self,
registry: &sprite_model::SpriteModelRegistry,
instances: &[sprite_model::SpriteInstance],
) {
if instances.is_empty() {
self.sprite_registry = None;
return;
}
self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
&self.device,
registry,
instances,
));
}
pub fn append_sprite_instances(
&mut self,
registry: &sprite_model::SpriteModelRegistry,
instances: &[sprite_model::SpriteInstance],
) -> u32 {
match self.sprite_registry.as_mut() {
Some(reg) => reg.append_instances(&self.device, registry, instances),
None => {
self.set_sprite_instances(registry, instances);
0
}
}
}
pub fn remove_sprite_instance(&mut self, index: usize) -> Option<usize> {
self.sprite_registry
.as_mut()
.and_then(|reg| reg.remove_instance(index))
}
pub fn flush_writes(&self) {
self.queue.submit(std::iter::empty::<wgpu::CommandBuffer>());
}
pub fn add_sprite_model(
&mut self,
registry: &sprite_model::SpriteModelRegistry,
chain_id: u32,
) {
match self.sprite_registry.as_mut() {
Some(reg) => reg.add_model(&self.device, &self.queue, registry, chain_id),
None => {
self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
&self.device,
registry,
&[],
));
}
}
}
pub fn remove_sprite_model(&mut self, chain_id: u32) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.remove_model(chain_id);
}
}
pub fn compact_sprite_models(&mut self, registry: &sprite_model::SpriteModelRegistry) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.compact(&self.device, &self.queue, registry);
}
}
#[must_use]
pub fn sprite_model_count(&self) -> usize {
self.sprite_registry
.as_ref()
.map_or(0, sprite_model::SpriteRegistryResident::live_model_count)
}
#[must_use]
pub fn dead_sprite_model_count(&self) -> usize {
self.sprite_registry
.as_ref()
.map_or(0, sprite_model::SpriteRegistryResident::dead_model_count)
}
#[must_use]
pub fn sprite_instance_count(&self) -> usize {
self.sprite_registry
.as_ref()
.map_or(0, sprite_model::SpriteRegistryResident::instance_count)
}
pub fn update_sprite_instance_transforms(
&mut self,
instances: &[sprite_model::SpriteInstance],
) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.update_transforms(instances);
}
}
pub fn update_sprite_model(
&mut self,
registry: &sprite_model::SpriteModelRegistry,
chain_id: u32,
) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.update_model(&self.device, &self.queue, registry, chain_id);
}
}
pub fn set_sprite_instance_model(
&mut self,
registry: &sprite_model::SpriteModelRegistry,
index: usize,
chain_id: u32,
) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.set_instance_model(registry, index, chain_id);
}
}
pub fn set_sprite_instance_colmul(&mut self, tables: &[[u64; 256]]) {
if let Some(reg) = self.sprite_registry.as_mut() {
reg.set_instance_colmul(tables);
}
}
pub fn set_sprite_lod_px(&mut self, px: f32) {
self.sprite_lod_px = px.max(0.25);
}
pub fn set_scene_mip_scan_dist(&mut self, dist: f32) {
self.scene_mip_scan_dist = dist.max(0.0);
}
pub fn set_scene_side_shades(&mut self, s: [i8; 6]) {
let v = |i: usize| i32::from(s[i] as u8);
self.scene_side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
}
fn build_sprite_model_dda(&self) -> SpriteModelDdaResources {
let capable = self.sprite_shadows_capable;
let src = sprite_shader_source(capable);
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sprite_model_dda.wgsl"),
source: wgpu::ShaderSource::Wgsl(src.into()),
});
let mut entries = vec![
bgl_uniform_entry(0),
bgl_storage_entry(1, true), bgl_storage_entry(2, true), bgl_storage_entry(3, true), bgl_storage_entry(4, true), bgl_storage_entry(5, true), bgl_storage_entry(6, true), bgl_storage_entry(7, false), bgl_storage_entry(8, true), bgl_storage_entry(9, true), bgl_storage_entry(10, true), bgl_storage_entry(11, true), bgl_storage_entry(12, true), bgl_storage_entry(13, true), bgl_storage_entry(15, true), ];
if capable {
entries.push(bgl_storage_entry(16, true)); entries.push(bgl_storage_entry(17, true)); entries.push(bgl_storage_entry(18, true)); entries.push(bgl_storage_entry(19, true)); entries.push(bgl_storage_entry(20, true)); entries.push(bgl_storage_entry(21, true)); entries.push(bgl_storage_entry(22, true)); entries.push(bgl_storage_entry(23, true)); }
let bgl = self
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("roxlap-gpu sprite_model_dda.bgl"),
entries: &entries,
});
let pl = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("roxlap-gpu sprite_model_dda.layout"),
bind_group_layouts: &[Some(&bgl)],
immediate_size: 0,
});
let pipeline = self
.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("roxlap-gpu sprite_model_dda.pipeline"),
layout: Some(&pl),
module: &shader,
entry_point: Some("march"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu sprite_model_dda.uniform"),
size: std::mem::size_of::<SpriteModelUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let materials_buf = {
use wgpu::util::DeviceExt;
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu sprite_model_dda.materials"),
contents: bytemuck::cast_slice(self.sprite_materials.as_slice()),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
})
};
SpriteModelDdaResources {
bgl,
pipeline,
uniform_buf,
materials_buf,
}
}
pub fn set_sprite_materials(&mut self, table: &roxlap_formats::material::MaterialTable) {
let (palette, any_translucent) = material_palette(table);
self.sprite_materials = palette;
self.sprite_has_translucent = any_translucent;
if let Some(smd) = &self.sprite_model_dda {
self.queue.write_buffer(
&smd.materials_buf,
0,
bytemuck::cast_slice(self.sprite_materials.as_slice()),
);
}
}
pub fn set_scene_terrain_materials(
&mut self,
table: &roxlap_formats::material::MaterialTable,
map: &[(Rgb, u8)],
) {
let (palette, _) = material_palette(table);
self.scene_materials = palette;
self.scene_terrain_map = map
.iter()
.take(256)
.map(|&(c, m)| [c.0 & 0x00ff_ffff, u32::from(m)])
.collect();
self.scene_terrain_translucent = map.iter().any(|&(_, m)| !table.get(m).is_opaque());
if let Some(dda) = &self.scene_dda {
self.queue.write_buffer(
&dda.materials_pal_buf,
0,
bytemuck::cast_slice(self.scene_materials.as_slice()),
);
if !self.scene_terrain_map.is_empty() {
self.queue.write_buffer(
&dda.terrain_map_buf,
0,
bytemuck::cast_slice(&self.scene_terrain_map),
);
}
}
}
}
pub struct HeadlessSceneRenderer {
width: u32,
height: u32,
framebuffer: wgpu::Buffer,
depth_buffer: wgpu::Buffer,
uniform_buf: wgpu::Buffer,
_sky_texture: wgpu::Texture,
sky_view: wgpu::TextureView,
sky_sampler: wgpu::Sampler,
bgl: wgpu::BindGroupLayout,
pipeline: wgpu::ComputePipeline,
readback: wgpu::Buffer,
side_shades: [[i32; 4]; 2],
lights: SceneLights,
}
impl HeadlessSceneRenderer {
#[must_use]
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self {
let framebuffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu headless.framebuffer"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu headless.uniform"),
size: std::mem::size_of::<SceneDdaUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let depth_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu headless.depth"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let default_sky_pixel = [120u8, 150, 220, 255];
let (sky_texture, sky_view) = create_sky_texture(device, 1, 1, &default_sky_pixel);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &sky_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&default_sky_pixel,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("roxlap-gpu headless.sky_sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene_dda.wgsl (headless)"),
source: wgpu::ShaderSource::Wgsl(crate::shader_src::scene_shader_source(false).into()),
});
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("roxlap-gpu headless.bgl"),
entries: &[
bgl_uniform_entry(0),
bgl_storage_entry(1, true),
bgl_storage_entry(2, true),
bgl_storage_entry(3, true),
bgl_storage_entry(4, true),
bgl_storage_entry(5, true),
bgl_storage_entry(6, true),
bgl_storage_entry(7, true),
bgl_storage_entry(8, false),
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
bgl_storage_entry(11, false),
bgl_storage_entry(12, true),
bgl_storage_entry(13, true),
bgl_storage_entry(14, true),
bgl_storage_entry(15, true),
bgl_storage_entry(16, true),
bgl_storage_entry(17, true),
bgl_storage_entry(18, true),
],
});
let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("roxlap-gpu headless.layout"),
bind_group_layouts: &[Some(&bgl)],
immediate_size: 0,
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("roxlap-gpu headless.pipeline"),
layout: Some(&pl),
module: &shader,
entry_point: Some("render_scene"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let readback = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("roxlap-gpu headless.readback"),
size: u64::from(width) * u64::from(height) * 4,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
Self {
width,
height,
framebuffer,
depth_buffer,
uniform_buf,
_sky_texture: sky_texture,
sky_view,
sky_sampler,
bgl,
pipeline,
readback,
side_shades: [[0; 4]; 2],
lights: SceneLights::default(),
}
}
pub fn set_side_shades(&mut self, s: [i8; 6]) {
let v = |i: usize| i32::from(s[i] as u8);
self.side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn render(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
scene: &GpuSceneResident,
cameras: &[Camera],
fov_y_rad: f32,
max_outer_steps: u32,
mip_scan_dist: f32,
) -> Vec<u32> {
self.render_with_transforms(
device,
queue,
scene,
cameras,
&[],
fov_y_rad,
max_outer_steps,
mip_scan_dist,
)
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn render_with_transforms(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
scene: &GpuSceneResident,
cameras: &[Camera],
grid_world: &[GridWorldTransform],
fov_y_rad: f32,
max_outer_steps: u32,
mip_scan_dist: f32,
) -> Vec<u32> {
assert_eq!(
cameras.len(),
scene.grid_count as usize,
"headless render: {} cameras for {} grids",
cameras.len(),
scene.grid_count,
);
let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
.iter()
.map(SceneDdaPerGridCamera::from_camera)
.collect();
for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
c.set_world_transform(t);
}
let (dummy_pal, dummy_map) = {
use wgpu::util::DeviceExt;
let pal: Vec<MaterialGpu> = vec![
MaterialGpu {
alpha: 1.0,
mode: 0
};
256
];
let p = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu headless.materials_pal"),
contents: bytemuck::cast_slice(&pal),
usage: wgpu::BufferUsages::STORAGE,
});
let m = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu headless.terrain_map"),
contents: bytemuck::cast_slice(&[[0u32; 2]]),
usage: wgpu::BufferUsages::STORAGE,
});
(p, m)
};
let dl = self.lights.clone();
inject_grid_sun_dirs(&dl, &mut cam_vec);
let (packed_lights, sun_flags, point_count) =
pack_scene_lights(&dl, scene.grid_count as usize);
let dummy_point_lights = upload_grid_point_lights(device, &packed_lights);
let grid_cameras = upload_grid_cameras(device, &cam_vec);
let uniform = SceneDdaUniform {
fov_y_rad,
grid_count: scene.grid_count,
max_outer_steps,
_pad0: 0,
screen_size: [self.width, self.height],
_pad1: [0; 2],
fog_color: [0.0, 0.0, 0.0, 1.0e29],
fog_far: 1.0e30,
write_depth: 0,
occ_page_words: scene.occupancy_page_words,
occ_num_pages: scene.occupancy_num_pages,
mip_scan_dist,
terrain_has_translucent: 0, terrain_map_count: 0,
_pad4: 0,
sky_cam: SceneDdaPerGridCamera::from_camera(&cameras.first().copied().unwrap_or(
Camera {
position: [0.0; 3],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
fov_y_rad,
},
)),
side_shades0: self.side_shades[0],
side_shades1: self.side_shades[1],
sun_color: [
dl.sun_color[0],
dl.sun_color[1],
dl.sun_color[2],
dl.sun_intensity,
],
ambient_color: [
dl.ambient[0],
dl.ambient[1],
dl.ambient[2],
dl.shadow_strength,
],
sun_flags,
point_light_count: point_count,
shadow_max_steps: dl.shadow_max_steps,
_pad5: 0,
shadow_bias: dl.shadow_bias,
shadow_max_dist: dl.shadow_max_dist,
_pad6: [0.0; 2],
shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
style_bands: dl.style_bands,
sprite_cast_count: 0, _pad7: [0; 2],
};
queue.write_buffer(&self.uniform_buf, 0, bytemuck::bytes_of(&uniform));
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("roxlap-gpu headless.bg"),
layout: &self.bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: scene.occupancy_pages[0].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: scene.all_color_offsets.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: scene.all_colors.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: scene.all_chunk_colors_base.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: scene.all_chunk_occupancy.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 6,
resource: scene.grid_static_meta.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: scene.all_slot_chunk_idx.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 8,
resource: self.framebuffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::TextureView(&self.sky_view),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
},
wgpu::BindGroupEntry {
binding: 11,
resource: self.depth_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 12,
resource: scene.occupancy_pages[1].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 13,
resource: scene.occupancy_pages[2].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 14,
resource: scene.occupancy_pages[3].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 15,
resource: grid_cameras.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 16,
resource: dummy_pal.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 17,
resource: dummy_map.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 18,
resource: dummy_point_lights.as_entire_binding(),
},
],
});
let mut enc =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("roxlap-gpu headless.pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.dispatch_workgroups(self.width.div_ceil(8), self.height.div_ceil(8), 1);
}
enc.copy_buffer_to_buffer(
&self.framebuffer,
0,
&self.readback,
0,
u64::from(self.width) * u64::from(self.height) * 4,
);
queue.submit(Some(enc.finish()));
let slice = self.readback.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |r| {
let _ = tx.send(r);
});
device.poll(wgpu::PollType::wait_indefinitely()).ok();
rx.recv().expect("map_async channel").expect("map_async");
let data = slice.get_mapped_range();
let out: Vec<u32> = data
.chunks_exact(4)
.map(|px| u32::from_le_bytes([px[0], px[1], px[2], px[3]]))
.collect();
drop(data);
self.readback.unmap();
out
}
}
fn bgl_uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn bgl_storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn create_sky_texture(
device: &wgpu::Device,
width: u32,
height: u32,
_initial_pixels: &[u8],
) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("roxlap-gpu sky_texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
pub(crate) fn pick_required_limits(adapter_limits: &wgpu::Limits) -> wgpu::Limits {
wgpu::Limits {
max_storage_buffer_binding_size: adapter_limits.max_storage_buffer_binding_size,
max_buffer_size: adapter_limits.max_buffer_size,
max_storage_buffers_per_shader_stage: adapter_limits
.max_storage_buffers_per_shader_stage
.min(SPRITE_SHADOW_MIN_STORAGE_BUFFERS),
..wgpu::Limits::default()
}
}
pub(crate) const SPRITE_SHADOW_MIN_STORAGE_BUFFERS: u32 = 22;
fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode {
for &m in &[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Immediate] {
if modes.contains(&m) {
return m;
}
}
wgpu::PresentMode::Fifo
}