From 3ba2fa065efa18294fa45e04343a7afcad42267a Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Tue, 13 Feb 2024 13:53:18 +0100 Subject: [PATCH] progress on offscreen rendering --- assets/generated/.gitignore | 1 + assets/models/bakery.glb | 4 +- assets/models/cinema.glb | 4 +- assets/shaders/pbr/render.wgsl | 95 +++++---- assets/shaders/pixel.frag.wgsl | 48 +++-- assets/shaders/shadow.wgsl | 6 +- assets/shaders/terrain/terrain.frag.wgsl | 34 +-- assets/shaders/water.frag.wgsl | 4 - common/src/hash.rs | 12 ++ engine/Cargo.toml | 2 +- engine/src/drawables/instanced_mesh.rs | 4 +- engine/src/drawables/lit_mesh.rs | 258 ++++++++++++++--------- engine/src/drawables/spritebatch.rs | 6 +- engine/src/drawables/terrain.rs | 6 +- engine/src/drawables/water.rs | 6 +- engine/src/gfx.rs | 124 +++++------ engine/src/passes/background.rs | 4 +- engine/src/passes/blur.rs | 4 +- engine/src/passes/fog.rs | 4 +- engine/src/passes/pbr.rs | 14 +- engine/src/passes/ssao.rs | 4 +- engine/src/pipeline_builder.rs | 6 +- engine/src/pipelines.rs | 66 ++++-- engine/src/texture.rs | 108 +++++++++- engine/src/yakui.rs | 8 +- geom/src/infinite_frustrum.rs | 10 + geom/src/perp_camera.rs | 2 +- geom/src/plane.rs | 2 +- native_app/src/game_loop.rs | 5 +- native_app/src/gui/mod.rs | 2 +- native_app/src/init.rs | 4 +- native_app/src/newgui/hud/mod.rs | 20 +- native_app/src/newgui/mod.rs | 63 ++++++ simulation/src/map/electricity_cache.rs | 8 +- 34 files changed, 636 insertions(+), 312 deletions(-) create mode 100644 assets/generated/.gitignore diff --git a/assets/generated/.gitignore b/assets/generated/.gitignore new file mode 100644 index 000000000..f59ec20aa --- /dev/null +++ b/assets/generated/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/assets/models/bakery.glb b/assets/models/bakery.glb index 9c8dee456..9f398f451 100644 --- a/assets/models/bakery.glb +++ b/assets/models/bakery.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3abebcbf3473790e7be0d26da91afb35a73e515c0970d9dfb91dc53b24ee558 -size 25180 +oid sha256:d3d415f0b3d25e4dc67824b9cf3b9ffef33c64bdeb57802080835ff230d89d9e +size 25116 diff --git a/assets/models/cinema.glb b/assets/models/cinema.glb index 3b5dfd4eb..d08711b66 100644 --- a/assets/models/cinema.glb +++ b/assets/models/cinema.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:140a19f04a73fa502e966fa39ef5a860c91c5f1f17dc4969bad0ddcf8c29da77 -size 20468 +oid sha256:6dacc82ebfdd11acb2c1b0fb7c2e005b2f1e597954182026dd39d1a939496c54 +size 20428 diff --git a/assets/shaders/pbr/render.wgsl b/assets/shaders/pbr/render.wgsl index 010d6d689..5d83f29cd 100644 --- a/assets/shaders/pbr/render.wgsl +++ b/assets/shaders/pbr/render.wgsl @@ -91,6 +91,56 @@ fn calc_light(Lo: vec3, return Lo + (kD * albedo * (0.7 + ssao * 0.3) / PI + specular_light) * shadow_v * col * NdotL; } +struct LightData { + data: vec4, + data2: vec4, + chunk_id: vec2, +}; + +fn get_lightdata(t_lightdata: texture_2d, t_lightdata2: texture_2d, wpos: vec3) -> LightData { + let chunk_id: vec2 = vec2(u32(wpos.x / LIGHTCHUNK_SIZE), u32(wpos.y / LIGHTCHUNK_SIZE)); + let lightdata: vec4 = textureLoad(t_lightdata, chunk_id, 0); + var lightdata2: vec4; + if(lightdata.w != 0u) { + lightdata2 = textureLoad(t_lightdata2, chunk_id, 0); + } else { + lightdata2 = vec4(0u); + } + return LightData(lightdata, lightdata2, chunk_id); +} + +fn calc_packed_light(Lo_: vec3, + chunk_id: vec2, + data: vec4, + V: vec3, + normal: vec3, + albedo: vec3, + metallic: f32, + roughness: f32, + F0: vec3, + wpos: vec3, + ssao: f32, + ) -> vec3 { + var Lo = Lo_; + if(data.x != 0u) { + let light = decodeLight(chunk_id, data.x); + Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, vec3(lightPower(wpos, light)), 1.0, ssao); + } + if(data.y != 0u) { + let light = decodeLight(chunk_id, data.y); + Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, vec3(lightPower(wpos, light)), 1.0, ssao); + } + if(data.z != 0u) { + let light = decodeLight(chunk_id, data.z); + Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, vec3(lightPower(wpos, light)), 1.0, ssao); + } + if(data.w != 0u) { + let light = decodeLight(chunk_id, data.w); + Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, vec3(lightPower(wpos, light)), 1.0, ssao); + } + return Lo; +} + fn render(sun: vec3, V: vec3, position: vec2, @@ -105,14 +155,10 @@ fn render(sun: vec3, roughness: f32, shadow_v: f32, ssao: f32, - t_lightdata: texture_2d, - t_lightdata2: texture_2d, + lightdata: LightData, wpos: vec3, fog: vec3, ) -> vec3 { - let chunk_id: vec2 = vec2(u32(wpos.x / LIGHTCHUNK_SIZE), u32(wpos.y / LIGHTCHUNK_SIZE)); - let lightdata: vec4 = textureLoad(t_lightdata, chunk_id, 0); - var Lo: vec3 = vec3(0.0); @@ -120,44 +166,13 @@ fn render(sun: vec3, Lo = calc_light(vec3(0.0), sun, V, normal, albedo, metallic, roughness, F0, sun_col, shadow_v, ssao); } if(sun.z < 0.1) { - if(lightdata.x != 0u) { - let light = decodeLight(chunk_id, lightdata.x); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - if(lightdata.y != 0u) { - let light = decodeLight(chunk_id, lightdata.y); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - if(lightdata.z != 0u) { - let light = decodeLight(chunk_id, lightdata.z); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - if(lightdata.w != 0u) { - let light = decodeLight(chunk_id, lightdata.w); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - let lightdata2: vec4 = textureLoad(t_lightdata2, chunk_id, 0); - if(lightdata2.x != 0u) { - let light = decodeLight(chunk_id, lightdata2.x); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); + Lo = calc_packed_light(Lo, lightdata.chunk_id, lightdata.data, V, normal, albedo, metallic, roughness, F0, wpos, ssao); + if (lightdata.data.w != 0) { + Lo = calc_packed_light(Lo, lightdata.chunk_id, lightdata.data2, V, normal, albedo, metallic, roughness, F0, wpos, ssao); } - if(lightdata2.y != 0u) { - let light = decodeLight(chunk_id, lightdata2.y); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - if(lightdata2.z != 0u) { - let light = decodeLight(chunk_id, lightdata2.z); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - if(lightdata2.w != 0u) { - let light = decodeLight(chunk_id, lightdata2.w); - Lo = calc_light(Lo, normalize(light - wpos), V, normal, albedo, metallic, roughness, F0, lightPower(wpos, light) * vec3(1.0), 1.0, ssao); - } - } } - let dkS: vec3 = F_spec; - var dkD: vec3 = 1.0 - dkS; - dkD *= 1.0 - vec3(metallic); + let dkD: vec3 = (1.0 - F_spec) * (1.0 - vec3(metallic)); let ambient: vec3 = (0.2 * dkD * (0.04 + irradiance_diffuse) * albedo + specular) * ssao; var color: vec3 = ambient + Lo + fog; diff --git a/assets/shaders/pixel.frag.wgsl b/assets/shaders/pixel.frag.wgsl index 4500dc39f..2d637b6af 100644 --- a/assets/shaders/pixel.frag.wgsl +++ b/assets/shaders/pixel.frag.wgsl @@ -16,24 +16,27 @@ struct MaterialParams { @group(0) @binding(0) var params: RenderParams; -@group(1) @binding(0) var t_ssao: texture_2d; -@group(1) @binding(1) var s_ssao: sampler; -@group(1) @binding(2) var t_fog: texture_2d; -@group(1) @binding(3) var s_fog: sampler; -@group(1) @binding(4) var t_bnoise: texture_2d; -@group(1) @binding(5) var s_bnoise: sampler; -@group(1) @binding(6) var t_sun_smap: texture_depth_2d_array; -@group(1) @binding(7) var s_sun_smap: sampler_comparison; -@group(1) @binding(8) var t_diffuse_irradiance: texture_cube; -@group(1) @binding(9) var s_diffuse_irradiance: sampler; -@group(1) @binding(10) var t_prefilter_specular: texture_cube; -@group(1) @binding(11) var s_prefilter_specular: sampler; -@group(1) @binding(12) var t_brdf_lut: texture_2d; -@group(1) @binding(13) var s_brdf_lut: sampler; +@group(1) @binding(0) var t_bnoise: texture_2d; +@group(1) @binding(1) var s_bnoise: sampler; +@group(1) @binding(2) var t_sun_smap: texture_depth_2d_array; +@group(1) @binding(3) var s_sun_smap: sampler_comparison; +@group(1) @binding(4) var t_diffuse_irradiance: texture_cube; +@group(1) @binding(5) var s_diffuse_irradiance: sampler; +@group(1) @binding(6) var t_prefilter_specular: texture_cube; +@group(1) @binding(7) var s_prefilter_specular: sampler; +@group(1) @binding(8) var t_brdf_lut: texture_2d; +@group(1) @binding(9) var s_brdf_lut: sampler; + +#ifndef OFFSCREEN_RENDER +@group(1) @binding(10) var t_ssao: texture_2d; +@group(1) @binding(11) var s_ssao: sampler; +@group(1) @binding(12) var t_fog: texture_2d; +@group(1) @binding(13) var s_fog: sampler; @group(1) @binding(14) var t_lightdata: texture_2d; @group(1) @binding(15) var s_lightdata: sampler; @group(1) @binding(16) var t_lightdata2: texture_2d; @group(1) @binding(17) var s_lightdata2: sampler; +#endif @group(2) @binding(0) var t_albedo: texture_2d; @group(2) @binding(1) var s_albedo: sampler; @@ -60,12 +63,18 @@ fn frag(@location(0) in_tint: vec4, let albedo: vec4 = textureSample(t_albedo, s_albedo, in_uv); var ssao = 1.0; #ifdef SSAO + #ifndef OFFSCREEN_RENDER ssao = textureSample(t_ssao, s_ssao, position.xy / params.viewport).r; #endif + #endif var shadow_v: f32 = 1.0; if (params.shadow_mapping_resolution != 0) { + #ifdef OFFSCREEN_RENDER + shadow_v = sampleFirstShadow(in_wpos); + #else shadow_v = sampleShadow(in_wpos); + #endif } var normal = in_normal; @@ -106,6 +115,7 @@ fn frag(@location(0) in_tint: vec4, var fog = vec3(0.0); #ifdef FOG + #ifndef OFFSCREEN_RENDER var fogdist: vec4 = textureSampleLevel(t_fog, s_fog, position.xy / params.viewport, 0.0); if (abs(fogdist.a - dist) > 100.0) { @@ -118,6 +128,13 @@ fn frag(@location(0) in_tint: vec4, fog = fogdist.rgb; } + #endif + #endif + + #ifdef OFFSCREEN_RENDER + let lightdata = LightData(vec4(0), vec4(0), vec2(0)); + #else + let lightdata = get_lightdata(t_lightdata, t_lightdata2, in_wpos); #endif let final_rgb: vec3 = render(params.sun, @@ -134,8 +151,7 @@ fn frag(@location(0) in_tint: vec4, roughness, shadow_v, ssao, - t_lightdata, - t_lightdata2, + lightdata, in_wpos, fog ); diff --git a/assets/shaders/shadow.wgsl b/assets/shaders/shadow.wgsl index e621fc668..ad342402b 100644 --- a/assets/shaders/shadow.wgsl +++ b/assets/shaders/shadow.wgsl @@ -29,6 +29,10 @@ fn sampleShadow(in_wpos: vec3) -> f32 { return mix(s1, s2, blend); } +fn sampleFirstShadow(in_wpos: vec3) -> f32 { + return sampleOneShadow(in_wpos, 0); +} + fn sampleOneShadow(in_wpos: vec3, index: i32) -> f32 { let light_local: vec4 = params.sunproj[index] * vec4(in_wpos, 1.0); @@ -42,7 +46,7 @@ fn sampleOneShadow(in_wpos: vec3, index: i32) -> f32 { for (var y = -1 ; y <= 1 ; y++) { x = -1; for (; x <= 1; x++) { - let shadow_coord: vec3 = corrected + vec3(f32(x), f32(y), -1.0) * offset; + let shadow_coord: vec3 = corrected + vec3(f32(x), f32(y), 0.0) * offset; total += textureSampleCompare(t_sun_smap, s_sun_smap, shadow_coord.xy, index, shadow_coord.z); } } diff --git a/assets/shaders/terrain/terrain.frag.wgsl b/assets/shaders/terrain/terrain.frag.wgsl index 5a1097453..5269ebe6a 100644 --- a/assets/shaders/terrain/terrain.frag.wgsl +++ b/assets/shaders/terrain/terrain.frag.wgsl @@ -20,20 +20,20 @@ struct ChunkData { @group(1) @binding(7) var s_cliff: sampler; @group(1) @binding(8) var cdata: ChunkData; -@group(2) @binding(0) var t_ssao: texture_2d; -@group(2) @binding(1) var s_ssao: sampler; -@group(2) @binding(2) var t_fog: texture_2d; -@group(2) @binding(3) var s_fog: sampler; -@group(2) @binding(4) var t_bnoise: texture_2d; -@group(2) @binding(5) var s_bnoise: sampler; -@group(2) @binding(6) var t_sun_smap: texture_depth_2d_array; -@group(2) @binding(7) var s_sun_smap: sampler_comparison; -@group(2) @binding(8) var t_diffuse_irradiance: texture_cube; -@group(2) @binding(9) var s_diffuse_irradiance: sampler; -@group(2) @binding(10) var t_prefilter_specular: texture_cube; -@group(2) @binding(11) var s_prefilter_specular: sampler; -@group(2) @binding(12) var t_brdf_lut: texture_2d; -@group(2) @binding(13) var s_brdf_lut: sampler; +@group(2) @binding(0) var t_bnoise: texture_2d; +@group(2) @binding(1) var s_bnoise: sampler; +@group(2) @binding(2) var t_sun_smap: texture_depth_2d_array; +@group(2) @binding(3) var s_sun_smap: sampler_comparison; +@group(2) @binding(4) var t_diffuse_irradiance: texture_cube; +@group(2) @binding(5) var s_diffuse_irradiance: sampler; +@group(2) @binding(6) var t_prefilter_specular: texture_cube; +@group(2) @binding(7) var s_prefilter_specular: sampler; +@group(2) @binding(8) var t_brdf_lut: texture_2d; +@group(2) @binding(9) var s_brdf_lut: sampler; +@group(2) @binding(10) var t_ssao: texture_2d; +@group(2) @binding(11) var s_ssao: sampler; +@group(2) @binding(12) var t_fog: texture_2d; +@group(2) @binding(13) var s_fog: sampler; @group(2) @binding(14) var t_lightdata: texture_2d; @group(2) @binding(15) var s_lightdata: sampler; @group(2) @binding(16) var t_lightdata2: texture_2d; @@ -195,9 +195,10 @@ fn frag(@builtin(position) position: vec4, } else { fog = fogdist.rgb; } - #endif + let lightdata = get_lightdata(t_lightdata, t_lightdata2, in_wpos); + let final_rgb: vec3 = render(params.sun, V, position.xy, @@ -212,8 +213,7 @@ fn frag(@builtin(position) position: vec4, roughness, shadow_v, ssao, - t_lightdata, - t_lightdata2, + lightdata, in_wpos, fog ); diff --git a/assets/shaders/water.frag.wgsl b/assets/shaders/water.frag.wgsl index 541bed817..8fcee99ba 100644 --- a/assets/shaders/water.frag.wgsl +++ b/assets/shaders/water.frag.wgsl @@ -17,10 +17,6 @@ struct FragmentOutput { @group(3) @binding(0) var t_fog: texture_2d; @group(3) @binding(1) var s_fog: sampler; -fn sample_depth(coords: vec2) -> f32 { - return textureLoad(t_depth, coords, 0).r; -} - // wave is (length, amplitude, dir) fn gerstnerWaveNormal(p: vec2, t: f32) -> vec3 { let N_WAVES: i32 = 5; diff --git a/common/src/hash.rs b/common/src/hash.rs index f79c3e8b9..750c70876 100644 --- a/common/src/hash.rs +++ b/common/src/hash.rs @@ -2,6 +2,18 @@ use rustc_hash::FxHasher; use std::any::TypeId; use std::hash::{BuildHasher, Hash, Hasher}; +pub fn hash_iter(iter: I) -> u64 +where + I: IntoIterator, + I::Item: Hash, +{ + let mut hasher = FxHasher::default(); + for item in iter { + item.hash(&mut hasher); + } + hasher.finish() +} + #[inline] pub fn hash_u64(obj: T) -> u64 where diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 29ce8d4d1..ecd1aedde 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -29,7 +29,7 @@ profiling = { version = "1.0.1", default-features = false } rayon = "1.6" beul = "1.0.0" slotmapd = "1.0" -inline_tweak = "1.0.8" +inline_tweak = { version = "1.0.8", features = ["derive"] } egui-wgpu = { git = "https://github.com/emilk/egui" } cpal = "0.15.0" lewton = "0.10.2" diff --git a/engine/src/drawables/instanced_mesh.rs b/engine/src/drawables/instanced_mesh.rs index dbb100a83..42007d4a3 100644 --- a/engine/src/drawables/instanced_mesh.rs +++ b/engine/src/drawables/instanced_mesh.rs @@ -97,7 +97,7 @@ impl Drawable for InstancedMesh { for (mat, indices) in &lod_select.primitives { let mat = gfx.material(*mat); let pipeline = gfx.get_pipeline(MeshPipeline { - format: None, + offscreen_render: false, instanced: true, alpha: false, smap: false, @@ -128,7 +128,7 @@ impl Drawable for InstancedMesh { for (mat, indices) in &lod_select.primitives { let mat = gfx.material(*mat); rp.set_pipeline(gfx.get_pipeline(MeshPipeline { - format: None, + offscreen_render: false, instanced: true, alpha: mat.transparent, smap: shadow_cascade.is_some(), diff --git a/engine/src/drawables/lit_mesh.rs b/engine/src/drawables/lit_mesh.rs index bee379dec..55bdafc7d 100644 --- a/engine/src/drawables/lit_mesh.rs +++ b/engine/src/drawables/lit_mesh.rs @@ -1,11 +1,11 @@ use std::sync::Arc; use wgpu::{ - BindGroupLayout, Device, IndexFormat, MapMode, RenderPass, RenderPipeline, TextureFormat, - TextureUsages, VertexBufferLayout, + BindGroupLayout, Device, IndexFormat, RenderPass, RenderPipeline, TextureFormat, + TextureViewDescriptor, TextureViewDimension, VertexBufferLayout, }; -use geom::{Camera, LinearColor, Matrix4, Sphere, Vec2, Vec3}; +use geom::{Camera, InfiniteFrustrum, LinearColor, Matrix4, Plane, Sphere, Vec2, Vec3}; use crate::meshbuild::MeshLod; use crate::{ @@ -44,7 +44,7 @@ pub fn screen_coverage(gfx: &GfxContext, s: Sphere) -> f32 { #[derive(Clone, Copy, Hash)] pub(crate) struct MeshPipeline { - pub(crate) format: Option, + pub(crate) offscreen_render: bool, pub(crate) instanced: bool, pub(crate) alpha: bool, pub(crate) smap: bool, @@ -58,40 +58,61 @@ impl PipelineKey for MeshPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { let vert = if self.instanced { - mk_module("instanced_mesh.vert") + mk_module("instanced_mesh.vert", &[]) } else { - mk_module("lit_mesh.vert") + mk_module("lit_mesh.vert", &[]) }; let vb: &[VertexBufferLayout] = if self.instanced { VB_INSTANCED } else { VB }; if !self.depth { - let frag = mk_module("pixel.frag"); + let extra_defines = if self.offscreen_render { + &["OFFSCREEN_RENDER"] as &[&str] + } else { + &[] + }; + + let frag = mk_module("pixel.frag", extra_defines); - return PipelineBuilder::color( + let bglayout = match self.offscreen_render { + true => bg_layout_offscreen_render(&gfx.device), + false => bg_layout_litmesh(&gfx.device), + }; + + let layouts = [ + &Uniform::::bindgroup_layout(&gfx.device), + &bglayout, + &Material::bindgroup_layout(&gfx.device), + ]; + + let mut builder = PipelineBuilder::color( "lit_mesh", - &[ - &Uniform::::bindgroup_layout(&gfx.device), - &bg_layout_litmesh(&gfx.device), - &Material::bindgroup_layout(&gfx.device), - ], + &layouts, vb, &vert, &frag, - self.format.unwrap_or(gfx.sc_desc.format), + match self.offscreen_render { + true => TextureFormat::Rgba8UnormSrgb, + false => gfx.sc_desc.format, + }, ) - .with_samples(gfx.samples) - .build(&gfx.device); + .with_samples(gfx.samples); + + if self.offscreen_render { + builder = builder.with_depth_write(); + } + + return builder.build(&gfx.device); } if !self.alpha { return gfx.depth_pipeline(vb, &vert, None, self.smap); } - let frag = mk_module("alpha_discard.frag"); + let frag = mk_module("alpha_discard.frag", &[]); gfx.depth_pipeline_bglayout( vb, &vert, @@ -106,62 +127,135 @@ impl PipelineKey for MeshPipeline { } impl Mesh { - pub fn render_to_texture(&self, cam: &Camera, gfx: &GfxContext, dest: &Texture) {} - - pub fn render_to_image( + pub fn render_to_texture( &self, cam: &Camera, gfx: &GfxContext, - size: u32, - on_complete: impl FnOnce(image::RgbaImage) + Send + 'static, + dest: &Texture, + dest_msaa: &Texture, ) { - let target_image = TextureBuilder::empty(size, size, 1, TextureFormat::Bgra8UnormSrgb) - .with_usage(TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC) - .with_label("mesh_render_to_image") - .build_no_queue(&gfx.device); - let mut encoder = gfx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("mesh_render_to_image"), }); + let sun = Vec3::new(0.5, -1.3, 0.9).normalize(); + + let smap_mat = cam.build_sun_shadowmap_matrix(sun, 1024.0, &InfiniteFrustrum::EMPTY); + let mut params = gfx.render_params.clone(&gfx.device); let value = params.value_mut(); value.proj = cam.proj_cache; value.inv_proj = cam.inv_proj_cache; value.cam_dir = cam.dir(); value.cam_pos = cam.eye(); - value.viewport = Vec2::splat(size as f32); - value.sun = Vec3::new(1.0, 1.0, 1.0).normalize(); - value.sun_col = LinearColor::WHITE; + value.viewport = Vec2::new(dest.extent.width as f32, dest.extent.height as f32); + value.sun = sun; + value.sun_col = 3.5 * LinearColor::new(1.0, 0.95, 1.0, 1.0); + value.sun_shadow_proj = [ + smap_mat[0], + Matrix4::zero(), + Matrix4::zero(), + Matrix4::zero(), + ]; value.time = 0.0; value.time_always = 0.0; - value.shadow_mapping_resolution = 0; + value.shadow_mapping_resolution = 1024; params.upload_to_gpu(&gfx.queue); - let mut cpy = self.clone(); - let mut lod_cpy = cpy.lods[0].clone(); - lod_cpy.screen_coverage = f32::NEG_INFINITY; - lod_cpy.bounding_sphere = Sphere::new(Vec3::ZERO, 10000.0); - cpy.lods = vec![lod_cpy].into_boxed_slice(); + let depth = TextureBuilder::empty( + dest.extent.width, + dest.extent.height, + 1, + TextureFormat::Depth32Float, + ) + .with_sample_count(4) + .build_no_queue(&gfx.device); + + let mut smap = TextureBuilder::empty(1024, 1024, 1, TextureFormat::Depth32Float) + .build_no_queue(&gfx.device); + + let mut params_smap = params.clone(&gfx.device); + let value_smap = params_smap.value_mut(); + value_smap.proj = smap_mat[0]; + params_smap.upload_to_gpu(&gfx.queue); + + let smap_view = smap.mip_view(0); + + { + let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("mesh_render_to_image_shadow_map"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &smap_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + rp.set_bind_group(0, ¶ms_smap.bg, &[]); + + rp.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + rp.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32); + + for (mat, index_range) in &self.lods[0].primitives { + let mat = gfx.material(*mat); + rp.set_pipeline(gfx.get_pipeline(MeshPipeline { + offscreen_render: true, + instanced: false, + alpha: mat.transparent, + smap: true, + depth: true, + })); + + if mat.transparent { + rp.set_bind_group(1, &mat.bg, &[]); + } + rp.draw_indexed(index_range.clone(), 0, 0..1); + } + } + + smap.view = smap.texture.create_view(&TextureViewDescriptor { + dimension: Some(TextureViewDimension::D2Array), + ..Default::default() + }); + smap.sampler = gfx.device.create_sampler(&Texture::depth_compare_sampler()); + + let simplelit_bg = Texture::multi_bindgroup( + &[ + &gfx.read_texture("assets/sprites/blue_noise_512.png") + .expect("blue noise not initialized"), + &smap, + &gfx.pbr.diffuse_irradiance_cube, + &gfx.pbr.specular_prefilter_cube, + &gfx.pbr.split_sum_brdf_lut, + ], + &gfx.device, + &bg_layout_offscreen_render(&gfx.device), + ); { let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("mesh_render_to_image"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &target_image.view, - resolve_target: None, + view: &dest_msaa.view, + resolve_target: Some(&dest.view), ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &gfx.fbos.depth.view, + view: &depth.view, depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), + load: wgpu::LoadOp::Clear(0.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, @@ -171,57 +265,25 @@ impl Mesh { }); rp.set_bind_group(0, ¶ms.bg, &[]); - - cpy.draw(&gfx, &mut rp); + rp.set_bind_group(1, &simplelit_bg, &[]); + rp.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + rp.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32); + + for (mat, index_range) in &self.lods[0].primitives { + let mat = gfx.material(*mat); + rp.set_pipeline(gfx.get_pipeline(MeshPipeline { + offscreen_render: true, + instanced: false, + alpha: false, + smap: false, + depth: false, + })); + rp.set_bind_group(2, &mat.bg, &[]); + rp.draw_indexed(index_range.clone(), 0, 0..1); + } } - let image_data_buf = Arc::new(gfx.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("mesh_render_to_image"), - size: (4 * size * size) as u64, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - })); - - encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture: &target_image.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: &image_data_buf, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * size), // 4 bytes per pixel * 4 pixels per row - rows_per_image: None, - }, - }, - wgpu::Extent3d { - width: size, - height: size, - depth_or_array_layers: 1, - }, - ); - gfx.queue.submit(std::iter::once(encoder.finish())); - - let image_data_buf_cpy = image_data_buf.clone(); - image_data_buf.slice(..).map_async(MapMode::Read, move |v| { - if v.is_err() { - log::error!("Failed to map buffer for reading for render_to_image"); - return; - } - - let v = image_data_buf_cpy.slice(..).get_mapped_range(); - - let Some(rgba) = image::RgbaImage::from_raw(size, size, v.to_vec()) else { - log::error!("Failed to create image from buffer for render_to_image"); - return; - }; - - on_complete(rgba); - }); } } @@ -238,7 +300,7 @@ impl Drawable for Mesh { for (mat, index_range) in &lod.primitives { let mat = gfx.material(*mat); rp.set_pipeline(gfx.get_pipeline(MeshPipeline { - format: None, + offscreen_render: false, instanced: false, alpha: false, smap: false, @@ -269,7 +331,7 @@ impl Drawable for Mesh { for (mat, index_range) in &lod.primitives { let mat = gfx.material(*mat); rp.set_pipeline(gfx.get_pipeline(MeshPipeline { - format: None, + offscreen_render: false, instanced: false, alpha: mat.transparent, smap: shadow_cascade.is_some(), @@ -289,22 +351,26 @@ impl Drawable for Mesh { } } -pub struct LitMeshDepth; -pub struct LitMeshDepthSMap; - pub fn bg_layout_litmesh(device: &Device) -> BindGroupLayout { Texture::bindgroup_layout( device, [ - TL::Float, - TL::Float, TL::Float, TL::DepthArray, TL::Cube, TL::Cube, TL::Float, + TL::Float, + TL::Float, TL::UInt, TL::UInt, ], ) } + +pub fn bg_layout_offscreen_render(device: &Device) -> BindGroupLayout { + Texture::bindgroup_layout( + device, + [TL::Float, TL::DepthArray, TL::Cube, TL::Cube, TL::Float], + ) +} diff --git a/engine/src/drawables/spritebatch.rs b/engine/src/drawables/spritebatch.rs index bbf50c02a..e84c6163a 100644 --- a/engine/src/drawables/spritebatch.rs +++ b/engine/src/drawables/spritebatch.rs @@ -125,10 +125,10 @@ impl PipelineKey for SBPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { - let vert = &mk_module("spritebatch.vert"); - let frag = &mk_module("pixel.frag"); + let vert = &mk_module("spritebatch.vert", &[]); + let frag = &mk_module("pixel.frag", &[]); PipelineBuilder::color( "spritebatch", diff --git a/engine/src/drawables/terrain.rs b/engine/src/drawables/terrain.rs index 49915388e..4f131d92f 100644 --- a/engine/src/drawables/terrain.rs +++ b/engine/src/drawables/terrain.rs @@ -620,7 +620,7 @@ impl PipelineKey for TerrainPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { let terrainlayout = gfx .device @@ -635,10 +635,10 @@ impl PipelineKey for TerrainPipeline { .collect::>(), label: Some("terrain bindgroup layout"), }); - let vert = &mk_module("terrain/terrain.vert"); + let vert = &mk_module("terrain/terrain.vert", &[]); if !self.depth { - let frag = &mk_module("terrain/terrain.frag"); + let frag = &mk_module("terrain/terrain.frag", &[]); return PipelineBuilder::color( "terrain", diff --git a/engine/src/drawables/water.rs b/engine/src/drawables/water.rs index f90d924ba..3f6aa62ca 100644 --- a/engine/src/drawables/water.rs +++ b/engine/src/drawables/water.rs @@ -71,10 +71,10 @@ impl PipelineKey for WaterPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { - let vert = &mk_module("lit_mesh.vert"); - let frag = &mk_module("water.frag"); + let vert = &mk_module("lit_mesh.vert", &[]); + let frag = &mk_module("water.frag", &[]); let layouts = &[ &gfx.render_params.layout, diff --git a/engine/src/gfx.rs b/engine/src/gfx.rs index 2ef3dfa08..fed4d0adc 100644 --- a/engine/src/gfx.rs +++ b/engine/src/gfx.rs @@ -11,8 +11,8 @@ use wgpu::{ FrontFace, ImageCopyTexture, ImageDataLayout, InstanceDescriptor, MultisampleState, PipelineLayoutDescriptor, PrimitiveState, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, SamplerDescriptor, Surface, - SurfaceConfiguration, SurfaceTexture, TextureAspect, TextureFormat, TextureUsages, TextureView, - TextureViewDescriptor, TextureViewDimension, VertexBufferLayout, VertexState, + SurfaceConfiguration, SurfaceTexture, TextureFormat, TextureUsages, TextureView, + TextureViewDescriptor, VertexBufferLayout, VertexState, }; use winit::window::{Fullscreen, Window}; @@ -682,6 +682,7 @@ impl GfxContext { &self.device, name, &self.defines, + vec![], ) } @@ -718,7 +719,10 @@ impl GfxContext { encs.depth_prepass = Some(self.depth_prepass(objsref)); }); scope.spawn(|_| { - encs.smap = self.shadow_map_pass(objsref); + use rayon::prelude::*; + if self.render_params.value().shadow_mapping_resolution != 0 { + encs.smap = self.shadow_map_pass(objsref).par_bridge().collect(); + } }); scope.spawn(|_| { passes::render_ssao(self, &mut encs.before_main); @@ -737,7 +741,9 @@ impl GfxContext { } else { encs.pbr = self.pbr_prepass(); encs.depth_prepass = Some(self.depth_prepass(objsref)); - encs.smap = self.shadow_map_pass(objsref); + if self.render_params.value().shadow_mapping_resolution != 0 { + encs.smap = self.shadow_map_pass(objsref).collect(); + } passes::render_ssao(self, &mut encs.before_main); passes::render_fog(self, &mut encs.before_main); encs.main = Some(self.main_render_pass(&frame, objsref)); @@ -792,12 +798,7 @@ impl GfxContext { view: &self.fbos.color_msaa, resolve_target: Some(frame), ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - }), + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, })], @@ -834,63 +835,51 @@ impl GfxContext { Some(pbr_enc.finish()) } - fn shadow_map_pass(&self, objsref: &[Box]) -> Vec { - if self.render_params.value().shadow_mapping_resolution != 0 { - use rayon::prelude::*; - let results = self - .sun_params - .iter() - .enumerate() - .par_bridge() - .map(|(i, u)| { - profiling::scope!(&format!("cascade shadow pass {}", i)); - let mut smap_enc = - self.device - .create_command_encoder(&CommandEncoderDescriptor { - label: Some("shadow map encoder"), - }); - let sun_view = self - .sun_shadowmap - .texture - .create_view(&TextureViewDescriptor { - label: Some("sun shadow view"), - format: Some(self.sun_shadowmap.format), - dimension: Some(TextureViewDimension::D2), - aspect: TextureAspect::DepthOnly, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: i as u32, - array_layer_count: Some(1), - }); - let mut sun_shadow_pass = smap_enc.begin_render_pass(&RenderPassDescriptor { - label: Some("sun shadow pass"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &sun_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }), - timestamp_writes: None, - occlusion_query_set: None, - }); - - sun_shadow_pass.set_bind_group(0, &u.bg, &[]); - - for obj in objsref.iter() { - obj.draw_depth(self, &mut sun_shadow_pass, Some(&u.value().proj)); - } + fn shadow_map_pass<'a>( + &'a self, + objsref: &'a [Box], + ) -> impl Iterator + 'a { + self.sun_params.iter().enumerate().map(move |(i, u)| { + profiling::scope!(&format!("cascade shadow pass {}", i)); + let mut smap_enc = self + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("shadow map encoder"), + }); + let sun_view = self.sun_shadowmap.layer_view(i as u32); + self.shadow_map_one_pass(u, objsref, &sun_view, &mut smap_enc); + smap_enc.finish() + }) + } + + fn shadow_map_one_pass<'a>( + &'a self, + u: &Uniform, + objsref: &[Box], + shadowmap_view: &'a TextureView, + enc: &'a mut CommandEncoder, + ) { + profiling::scope!("cascade shadow pass"); + let mut sun_shadow_pass = enc.begin_render_pass(&RenderPassDescriptor { + label: Some("sun shadow pass"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &shadowmap_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - drop(sun_shadow_pass); + sun_shadow_pass.set_bind_group(0, &u.bg, &[]); - smap_enc.finish() - }) - .collect::>(); - return results; + for obj in objsref.iter() { + obj.draw_depth(self, &mut sun_shadow_pass, Some(&u.value().proj)); } - return vec![]; } fn depth_prepass(&self, objsref: &[Box]) -> CommandBuffer { @@ -1009,14 +998,15 @@ impl GfxContext { pub fn update_simplelit_bg(&mut self) { self.simplelit_bg = Texture::multi_bindgroup( &[ - &self.fbos.ssao, - &self.fbos.fog, - self.read_texture("assets/sprites/blue_noise_512.png") + &self + .read_texture("assets/sprites/blue_noise_512.png") .expect("blue noise not initialized"), &self.sun_shadowmap, &self.pbr.diffuse_irradiance_cube, &self.pbr.specular_prefilter_cube, &self.pbr.split_sum_brdf_lut, + &self.fbos.ssao, + &self.fbos.fog, &self.lamplights.lightdata, &self.lamplights.lightdata2, ], diff --git a/engine/src/passes/background.rs b/engine/src/passes/background.rs index 9657f8d83..f7da5ce0b 100644 --- a/engine/src/passes/background.rs +++ b/engine/src/passes/background.rs @@ -54,9 +54,9 @@ impl PipelineKey for BackgroundPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { - let bg = &mk_module("background"); + let bg = &mk_module("background", &[]); let render_pipeline_layout = gfx .device diff --git a/engine/src/passes/blur.rs b/engine/src/passes/blur.rs index ffc9fb741..399bcbcac 100644 --- a/engine/src/passes/blur.rs +++ b/engine/src/passes/blur.rs @@ -139,9 +139,9 @@ impl PipelineKey for UIBlurPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { - let bg = &mk_module("ui_blur"); + let bg = &mk_module("ui_blur", &[]); let l = Texture::bindgroup_layout(&gfx.device, [TL::Float]); diff --git a/engine/src/passes/fog.rs b/engine/src/passes/fog.rs index 84e8c0342..edae52b07 100644 --- a/engine/src/passes/fog.rs +++ b/engine/src/passes/fog.rs @@ -49,7 +49,7 @@ impl PipelineKey for FogPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { let render_pipeline_layout = gfx .device @@ -78,7 +78,7 @@ impl PipelineKey for FogPipeline { }), })]; - let fog = mk_module("fog"); + let fog = mk_module("fog", &[]); let render_pipeline_desc = RenderPipelineDescriptor { label: None, diff --git a/engine/src/passes/pbr.rs b/engine/src/passes/pbr.rs index 4d9b89ac4..c57f819c1 100644 --- a/engine/src/passes/pbr.rs +++ b/engine/src/passes/pbr.rs @@ -319,12 +319,12 @@ impl PipelineKey for PbrPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { match self { PbrPipeline::Environment => { - let cubemap_vert = &mk_module("to_cubemap.vert"); - let cubemap_frag = &mk_module("atmosphere_cubemap.frag"); + let cubemap_vert = &mk_module("to_cubemap.vert", &[]); + let cubemap_frag = &mk_module("atmosphere_cubemap.frag", &[]); let cubemappipelayout = gfx.device .create_pipeline_layout(&PipelineLayoutDescriptor { @@ -358,8 +358,8 @@ impl PipelineKey for PbrPipeline { }) } PbrPipeline::DiffuseIrradiance => { - let cubemap_vert = &mk_module("to_cubemap.vert"); - let cubemap_frag = &mk_module("pbr/convolute_diffuse_irradiance.frag"); + let cubemap_vert = &mk_module("to_cubemap.vert", &[]); + let cubemap_frag = &mk_module("pbr/convolute_diffuse_irradiance.frag", &[]); let bg_layout = Texture::bindgroup_layout(&gfx.device, [TL::Cube]); let params_layout = Uniform::<()>::bindgroup_layout(&gfx.device); @@ -396,8 +396,8 @@ impl PipelineKey for PbrPipeline { }) } PbrPipeline::SpecularPrefilter => { - let cubemap_vert = &mk_module("to_cubemap.vert"); - let cubemap_frag = &mk_module("pbr/specular_prefilter.frag"); + let cubemap_vert = &mk_module("to_cubemap.vert", &[]); + let cubemap_frag = &mk_module("pbr/specular_prefilter.frag", &[]); let bg_layout = Texture::bindgroup_layout(&gfx.device, [TL::Cube]); let params_layout = Uniform::<()>::bindgroup_layout(&gfx.device); diff --git a/engine/src/passes/ssao.rs b/engine/src/passes/ssao.rs index 8f9fa685d..61c884d14 100644 --- a/engine/src/passes/ssao.rs +++ b/engine/src/passes/ssao.rs @@ -41,7 +41,7 @@ impl PipelineKey for SSAOPipeline { fn build( &self, gfx: &GfxContext, - mut mk_module: impl FnMut(&str) -> CompiledModule, + mut mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline { let render_pipeline_layout = gfx .device @@ -70,7 +70,7 @@ impl PipelineKey for SSAOPipeline { }), })]; - let ssao = mk_module("ssao"); + let ssao = mk_module("ssao", &[]); let render_pipeline_desc = RenderPipelineDescriptor { label: None, diff --git a/engine/src/pipeline_builder.rs b/engine/src/pipeline_builder.rs index f281a77f7..e47fd9511 100644 --- a/engine/src/pipeline_builder.rs +++ b/engine/src/pipeline_builder.rs @@ -44,7 +44,7 @@ impl<'a> PipelineBuilder<'a> { }, depth_stencil: Some(wgpu::DepthStencilState { format: TextureFormat::Depth32Float, - depth_write_enabled: true, + depth_write_enabled: false, depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: Default::default(), bias: DepthBiasState { @@ -78,12 +78,12 @@ impl<'a> PipelineBuilder<'a> { self } - pub fn without_depth_write(mut self) -> Self { + pub fn with_depth_write(mut self) -> Self { self.descr .depth_stencil .as_mut() .unwrap() - .depth_write_enabled = false; + .depth_write_enabled = true; self } diff --git a/engine/src/pipelines.rs b/engine/src/pipelines.rs index 680bb1e28..2b7ac7c22 100644 --- a/engine/src/pipelines.rs +++ b/engine/src/pipelines.rs @@ -1,7 +1,7 @@ use crate::{compile_shader, CompiledModule, GfxContext}; use common::FastMap; use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::hash::Hash; use std::path::Path; use std::time::SystemTime; @@ -11,16 +11,17 @@ pub trait PipelineKey: Hash + 'static { fn build( &self, gfx: &GfxContext, - mk_module: impl FnMut(&str) -> CompiledModule, + mk_module: impl FnMut(&str, &[&str]) -> CompiledModule, ) -> RenderPipeline; } type ShaderPath = String; +type ShaderKey = (ShaderPath, Vec); type PipelineHash = u64; #[derive(Default)] pub struct Pipelines { - pub(crate) shader_cache: FastMap, + pub(crate) shader_cache: BTreeMap, pub(crate) shader_watcher: FastMap, Option)>, pub(crate) pipelines: HashMap, @@ -33,32 +34,47 @@ impl Pipelines { } pub fn get_module( - shader_cache: &mut FastMap, + shader_cache: &mut BTreeMap, shader_watcher: &mut FastMap, Option)>, device: &Device, name: &str, defines: &FastMap, + extra_defines: Vec, ) -> CompiledModule { - if let Some(v) = shader_cache.get(name) { + let key = (name.to_string(), extra_defines); + if let Some(v) = shader_cache.get(&key) { return v.clone(); } shader_cache - .entry(name.to_string()) + .entry(key) .or_insert_with_key(move |key| { - let module = compile_shader(device, key, defines); + let mut define_holder; + let total_defines = { + if key.1.is_empty() { + defines + } else { + define_holder = defines.clone(); + for k in &key.1 { + define_holder.insert(k.to_string(), "1".to_string()); + } + &define_holder + } + }; + + let module = compile_shader(device, name, total_defines); for dep in module.get_deps() { shader_watcher .entry(dep.trim_end_matches(".wgsl").to_string()) .or_insert((vec![], None)) .0 - .push(key.to_string()); + .push(key.0.to_string()); } shader_watcher - .entry(key.to_string()) + .entry(key.0.to_string()) .or_insert((vec![], None)) .0 - .push(key.to_string()); + .push(key.0.to_string()); module }) @@ -76,14 +92,16 @@ impl Pipelines { Entry::Occupied(o) => o.get(), Entry::Vacant(v) => { let mut deps = Vec::new(); - let pipeline = obj.build(gfx, |name| { + let pipeline = obj.build(gfx, |name, extra_defines| { deps.push(name.to_string()); + Pipelines::get_module( &mut self.shader_cache, &mut self.shader_watcher, device, name, &gfx.defines, + extra_defines.iter().map(|x| x.to_string()).collect(), ) }); for dep in deps { @@ -108,18 +126,36 @@ impl Pipelines { device: &Device, shader_name: &str, ) { - if let Some(x) = self.shader_cache.get_mut(shader_name) { + for ((name, extra_defines), x) in self + .shader_cache + .range_mut((shader_name.to_string(), vec![])..) + { + if name != shader_name { + break; + } device.push_error_scope(ErrorFilter::Validation); - let new_shader = compile_shader(device, shader_name, defines); + let mut define_holder; + let total_defines = { + if extra_defines.is_empty() { + defines + } else { + define_holder = defines.clone(); + for k in extra_defines { + define_holder.insert(k.to_string(), "1".to_string()); + } + &define_holder + } + }; + + let new_shader = compile_shader(device, shader_name, total_defines); let scope = beul::execute(device.pop_error_scope()); if scope.is_some() { log::error!("failed to compile shader for invalidation {}", shader_name); return; } *x = new_shader; - } else { - return; } + for hash in self .pipelines_deps .get_mut(shader_name) diff --git a/engine/src/texture.rs b/engine/src/texture.rs index a8053cf33..f31aceba1 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -3,14 +3,14 @@ use std::fs::File; use std::io; use std::io::Read; -use std::path::Path; -use std::sync::RwLock; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; use derive_more::{Display, From}; use image::{DynamicImage, GenericImageView}; use wgpu::{ BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, CommandEncoder, - CommandEncoderDescriptor, Device, Extent3d, ImageCopyTexture, ImageDataLayout, + CommandEncoderDescriptor, Device, Extent3d, ImageCopyTexture, ImageDataLayout, MapMode, PipelineLayoutDescriptor, RenderPipeline, SamplerDescriptor, TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, }; @@ -233,6 +233,73 @@ impl Texture { }) } + pub fn save_to_file(&self, device: &Device, queue: &wgpu::Queue, path: PathBuf) { + match self.format { + TextureFormat::Rgba8Unorm | TextureFormat::Rgba8UnormSrgb => {} + _ => { + log::error!("save_to_file not implemented for format {:?}", self.format); + return; + } + } + + debug_assert!(self.extent.depth_or_array_layers == 1); + + let block_size = self.format.block_copy_size(None).unwrap(); + + let image_data_buf = Arc::new(device.create_buffer(&wgpu::BufferDescriptor { + label: Some("save_to_file"), + size: (block_size * self.extent.width * self.extent.height) as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + })); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("save_to_file"), + }); + + encoder.copy_texture_to_buffer( + ImageCopyTexture { + texture: &self.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &image_data_buf, + layout: ImageDataLayout { + offset: 0, + bytes_per_row: Some(block_size * self.extent.width), + rows_per_image: None, + }, + }, + self.extent, + ); + + queue.submit(std::iter::once(encoder.finish())); + + let image_data_buf_cpy = image_data_buf.clone(); + let extent_cpy = self.extent; + image_data_buf.slice(..).map_async(MapMode::Read, move |v| { + if v.is_err() { + log::error!("Failed to map buffer for reading for save_to_file"); + return; + } + + let v = image_data_buf_cpy.slice(..).get_mapped_range(); + + let Some(rgba) = + image::RgbaImage::from_raw(extent_cpy.width, extent_cpy.height, v.to_vec()) + else { + log::error!("Failed to create image from buffer for save_to_file"); + return; + }; + + if let Err(e) = rgba.save(path) { + log::error!("Failed to save image to file: {}", e); + } + }); + } + pub fn depth_compare_sampler() -> SamplerDescriptor<'static> { SamplerDescriptor { label: None, @@ -286,6 +353,19 @@ impl Texture { array_layer_count: None, }) } + + pub fn layer_view(&self, layer: u32) -> TextureView { + self.texture.create_view(&TextureViewDescriptor { + label: Some("texture array one layer view"), + format: None, + dimension: Some(TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: layer, + array_layer_count: Some(1), + }) + } } #[derive(Debug, Display, From)] @@ -307,6 +387,7 @@ pub struct TextureBuilder<'a> { fixed_mipmaps: Option, usage: TextureUsages, no_anisotropy: bool, + sample_count: u32, } impl<'a> TextureBuilder<'a> { @@ -350,7 +431,7 @@ impl<'a> TextureBuilder<'a> { self } - pub(crate) fn from_path(p: impl AsRef) -> Self { + pub fn from_path(p: impl AsRef) -> Self { let r = p.as_ref(); match Self::try_from_path(r) { Ok(x) => x, @@ -365,7 +446,7 @@ impl<'a> TextureBuilder<'a> { } } - pub(crate) fn try_from_path(p: impl AsRef) -> Result { + pub fn try_from_path(p: impl AsRef) -> Result { let p = p.as_ref(); let mut buf = vec![]; let mut f = File::open(p)?; @@ -373,7 +454,7 @@ impl<'a> TextureBuilder<'a> { Self::from_bytes(&buf) } - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { /* if bytes.starts_with(b"#?RADIANCE") { let irradiance = radiant::load(bytes).ok()?; @@ -396,7 +477,7 @@ impl<'a> TextureBuilder<'a> { Ok(Self::from_img(img)) } - pub(crate) fn from_img(img: DynamicImage) -> Self { + pub fn from_img(img: DynamicImage) -> Self { Self { dimensions: (img.dimensions().0, img.dimensions().1, 1), img: Some(img), @@ -411,10 +492,11 @@ impl<'a> TextureBuilder<'a> { | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, no_anisotropy: false, + sample_count: 1, } } - pub(crate) fn empty(w: u32, h: u32, d: u32, format: TextureFormat) -> Self { + pub fn empty(w: u32, h: u32, d: u32, format: TextureFormat) -> Self { Self { img: None, sampler: Texture::linear_sampler(), @@ -429,9 +511,15 @@ impl<'a> TextureBuilder<'a> { | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, no_anisotropy: false, + sample_count: 1, } } + pub fn with_sample_count(mut self, samples: u32) -> Self { + self.sample_count = samples; + self + } + fn mip_level_count(&self) -> u32 { if self.mipmaps.is_some() || self.mipmaps_no_gen || self.fixed_mipmaps.is_some() { if let Some(v) = self.fixed_mipmaps { @@ -460,7 +548,7 @@ impl<'a> TextureBuilder<'a> { label: Some(self.label), size: extent, mip_level_count, - sample_count: 1, + sample_count: self.sample_count, dimension: wgpu::TextureDimension::D2, format, usage: self.usage, @@ -550,7 +638,7 @@ impl<'a> TextureBuilder<'a> { label: Some(self.label), size: extent, mip_level_count, - sample_count: 1, + sample_count: self.sample_count, dimension: wgpu::TextureDimension::D2, format, usage: self.usage, diff --git a/engine/src/yakui.rs b/engine/src/yakui.rs index 1c007ccd4..228a930c8 100644 --- a/engine/src/yakui.rs +++ b/engine/src/yakui.rs @@ -1,4 +1,4 @@ -use crate::{GfxContext, GuiRenderContext}; +use crate::{GfxContext, GuiRenderContext, Texture}; use std::path::PathBuf; use std::sync::Arc; use wgpu::{TextureFormat, TextureViewDescriptor}; @@ -55,8 +55,12 @@ impl YakuiWrapper { } } - pub fn add_texture(&mut self, gfx: &mut GfxContext, path: &PathBuf) -> TextureId { + pub fn load_texture(&mut self, gfx: &mut GfxContext, path: &PathBuf) -> TextureId { let tex = gfx.texture(path, "yakui texture"); + self.add_texture(&tex) + } + + pub fn add_texture(&mut self, tex: &Texture) -> TextureId { self.renderer.add_texture( Arc::new(tex.texture.create_view(&TextureViewDescriptor::default())), wgpu::FilterMode::Linear, diff --git a/geom/src/infinite_frustrum.rs b/geom/src/infinite_frustrum.rs index 2d6db72f3..a21cf7a7e 100644 --- a/geom/src/infinite_frustrum.rs +++ b/geom/src/infinite_frustrum.rs @@ -8,6 +8,16 @@ pub struct InfiniteFrustrum { } impl InfiniteFrustrum { + pub const EMPTY: Self = Self { + planes: [ + Plane::X, + Plane::new(Vec3::new(-1.0, 0.0, 0.0), 1.0), + Plane::X, + Plane::X, + Plane::X, + ], + }; + /// Create a new frustrum from the given planes. /// The planes must be in the following order: /// [near, left, right, bottom, top, far] diff --git a/geom/src/perp_camera.rs b/geom/src/perp_camera.rs index e506901ed..65533e0d9 100644 --- a/geom/src/perp_camera.rs +++ b/geom/src/perp_camera.rs @@ -56,7 +56,7 @@ impl Camera { let v = self.yaw.vec2(); let horiz = self.pitch.cos(); let vert = self.pitch.sin(); - (v * horiz).z(vert) + (v * horiz).z(vert).normalize() } pub fn offset(&self) -> Vec3 { diff --git a/geom/src/plane.rs b/geom/src/plane.rs index 099317c88..964229a8b 100644 --- a/geom/src/plane.rs +++ b/geom/src/plane.rs @@ -14,7 +14,7 @@ pub struct Plane { impl Plane { pub const X: Self = Self { n: Vec3::X, o: 0.0 }; - pub fn new(n: Vec3, o: f32) -> Self { + pub const fn new(n: Vec3, o: f32) -> Self { Self { n, o } } diff --git a/native_app/src/game_loop.rs b/native_app/src/game_loop.rs index ff3ea33e1..9caf5ccf6 100644 --- a/native_app/src/game_loop.rs +++ b/native_app/src/game_loop.rs @@ -12,6 +12,7 @@ use crate::audio::GameAudio; use crate::gui::windows::debug::DebugObjs; use crate::gui::{render_oldgui, ExitState, FollowEntity, GuiState, UiTextures}; use crate::inputmap::{Bindings, InputAction, InputMap}; +use crate::newgui; use crate::newgui::terraforming::TerraformingResource; use crate::newgui::windows::settings::{manage_settings, Settings}; use crate::newgui::{render_newgui, TimeAlways, Tool}; @@ -37,6 +38,7 @@ pub struct State { impl engine::framework::State for State { fn new(ctx: &mut Context) -> Self { + profiling::scope!("game_loop::init"); goryak::set_blur_texture(ctx.yakui.blur_bg_texture); let camera = OrbitCamera::load((ctx.gfx.size.0, ctx.gfx.size.1)); @@ -95,6 +97,7 @@ impl engine::framework::State for State { profiling::scope!("game_loop::update"); self.uiw.write::().0 += ctx.delta; + newgui::do_icons(&mut ctx.gfx, &mut self.uiw, &mut ctx.yakui); { let mut timings = self.uiw.write::(); timings.engine_time.add_value(ctx.engine_time); @@ -150,7 +153,7 @@ impl engine::framework::State for State { !ctx.egui.last_kb_captured, !ctx.egui.last_mouse_captured, ); - crate::newgui::run_ui_systems(&self.sim.read().unwrap(), &mut self.uiw); + newgui::run_ui_systems(&self.sim.read().unwrap(), &mut self.uiw); self.uiw.write::().all.add_value(ctx.delta); self.uiw.write::().per_game_system = self.game_schedule.times(); diff --git a/native_app/src/gui/mod.rs b/native_app/src/gui/mod.rs index 8d698e23a..93c49a315 100644 --- a/native_app/src/gui/mod.rs +++ b/native_app/src/gui/mod.rs @@ -46,7 +46,7 @@ impl UiTextures { ); textures.insert(name.clone(), h); - yakui_textures.insert(name, yakui.add_texture(gfx, &path)); + yakui_textures.insert(name, yakui.load_texture(gfx, &path)); } Self { textures, diff --git a/native_app/src/init.rs b/native_app/src/init.rs index 98a7a00c5..0d00f189c 100644 --- a/native_app/src/init.rs +++ b/native_app/src/init.rs @@ -14,7 +14,8 @@ use crate::newgui::windows::economy::EconomyState; use crate::newgui::windows::settings::{Settings, SettingsState}; use crate::newgui::zoneedit::ZoneEditState; use crate::newgui::{ - ErrorTooltip, InspectedBuilding, InspectedEntity, PotentialCommands, TimeAlways, Tool, + ErrorTooltip, IconTextures, InspectedBuilding, InspectedEntity, PotentialCommands, TimeAlways, + Tool, }; use crate::rendering::immediate::{ImmediateDraw, ImmediateSound}; use crate::uiworld::{ReceivedCommands, UiWorld}; @@ -63,6 +64,7 @@ pub fn init() { register_resource_noserialize::(); register_resource_noserialize::(); register_resource_noserialize::(); + register_resource_noserialize::(); } pub struct InitFunc { diff --git a/native_app/src/newgui/hud/mod.rs b/native_app/src/newgui/hud/mod.rs index 21ee0aaad..748f19a69 100644 --- a/native_app/src/newgui/hud/mod.rs +++ b/native_app/src/newgui/hud/mod.rs @@ -1,6 +1,8 @@ +use goryak::minrow; use ordered_float::OrderedFloat; use std::time::Instant; -use yakui::{reflow, Alignment, Color, Dim2, Vec2}; +use yakui::widgets::Pad; +use yakui::{image, reflow, Alignment, Color, Dim2, Vec2}; use simulation::map_dynamic::ElectricityFlow; use simulation::Simulation; @@ -10,6 +12,7 @@ use crate::newgui::hud::menu::menu_bar; use crate::newgui::hud::time_controls::time_controls; use crate::newgui::hud::toolbox::new_toolbox; use crate::newgui::windows::settings::Settings; +use crate::newgui::IconTextures; use crate::uiworld::{SaveLoadState, UiWorld}; mod menu; @@ -32,6 +35,21 @@ pub fn render_newgui(uiworld: &UiWorld, sim: &Simulation) { menu_bar(uiworld, sim); uiworld.write::().windows.render(uiworld, sim); time_controls(uiworld, sim); + goryak::Window { + title: "Building icons", + pad: Pad::all(5.0), + radius: 5.0, + opened: &mut true, + } + .show(|| { + minrow(5.0, || { + let ids = &uiworld.read::().ids; + + for id in ids { + image(*id, Vec2::splat(128.0)); + } + }); + }); }); //goryak::debug_layout(); } diff --git a/native_app/src/newgui/mod.rs b/native_app/src/newgui/mod.rs index 7a88a08ed..8fc8d30b6 100644 --- a/native_app/src/newgui/mod.rs +++ b/native_app/src/newgui/mod.rs @@ -1,8 +1,15 @@ use crate::uiworld::UiWorld; +use common::FastMap; +use engine::wgpu::TextureFormat; +use engine::yakui::YakuiWrapper; +use engine::{GfxContext, TextureBuilder}; +use geom::{Camera, Degrees, Vec3}; +use prototypes::{BuildingPrototypeID, RenderAsset}; use simulation::map::BuildingID; use simulation::world_command::WorldCommand; use simulation::{AnyEntity, Simulation}; use std::borrow::Cow; +use yakui::TextureId; mod hud; mod tools; @@ -10,6 +17,62 @@ mod tools; pub use hud::*; pub use tools::*; +#[derive(Default)] +pub struct IconTextures { + texs: FastMap, + ids: Vec, +} + +pub fn do_icons(gfx: &mut GfxContext, uiw: &mut UiWorld, yakui: &mut YakuiWrapper) { + let mut state = uiw.write::(); + + let mut cam = Camera::new(Vec3::new(0.0, 0.0, 0.0), 256.0, 256.0); + + cam.fovy = tweak!(30.0); + cam.pitch = Degrees(35.0).into(); + cam.yaw = Degrees(tweak!(-130.0)).into(); + + state.ids.clear(); + + for building in prototypes::BuildingPrototype::iter() { + let RenderAsset::Mesh { ref path } = building.asset else { + continue; + }; + //if state.texs.contains_key(&building.id) { + // continue; + //} + let Ok(mesh) = gfx.mesh(path.as_ref()) else { + continue; + }; + + let t = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) + .with_label("building icon") + .with_usage( + engine::wgpu::TextureUsages::COPY_DST + | engine::wgpu::TextureUsages::RENDER_ATTACHMENT + | engine::wgpu::TextureUsages::TEXTURE_BINDING, + ) + .build_no_queue(&gfx.device); + + let t_msaa = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) + .with_label("building icon msaa") + .with_usage(engine::wgpu::TextureUsages::RENDER_ATTACHMENT) + .with_sample_count(4) + .build_no_queue(&gfx.device); + + let aabb3 = mesh.lods[0].aabb3; + cam.pos = aabb3.center(); + cam.dist = aabb3.ll.distance(aabb3.ur); + cam.update(); + + mesh.render_to_texture(&cam, gfx, &t, &t_msaa); + let tex_id = yakui.add_texture(&t); + + state.texs.insert(building.id, t); + state.ids.push(tex_id); + } +} + pub fn run_ui_systems(sim: &Simulation, uiworld: &UiWorld) { profiling::scope!("gui::run_ui_systems"); bulldozer::bulldozer(sim, uiworld); diff --git a/simulation/src/map/electricity_cache.rs b/simulation/src/map/electricity_cache.rs index fd2655789..3e30416c5 100644 --- a/simulation/src/map/electricity_cache.rs +++ b/simulation/src/map/electricity_cache.rs @@ -251,22 +251,22 @@ impl ElectricityCache { match obj { NetworkObjectID::Building(b) => { let Some(b) = buildings.get(b) else { - return Left(Left(None.into_iter())); + return Left(Left(std::iter::empty())); }; let Some(r) = b.connected_road else { - return Left(Left(None.into_iter())); + return Left(Left(std::iter::empty())); }; Left(Right(Some(NetworkObjectID::Road(r)).into_iter())) } NetworkObjectID::Intersection(i) => { let Some(i) = intersections.get(i) else { - return Left(Left(None.into_iter())); + return Left(Left(std::iter::empty())); }; Right(Left(i.roads.iter().map(|v| NetworkObjectID::Road(*v)))) } NetworkObjectID::Road(r) => { let Some(r) = roads.get(r) else { - return Left(Left(None.into_iter())); + return Left(Left(std::iter::empty())); }; Right(Right(common::iter::chain(( Some(NetworkObjectID::Intersection(r.src)).into_iter(),