Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pomme-client/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ fn main() {
("item_entity.frag", shaderc::ShaderKind::Fragment),
("weather.vert", shaderc::ShaderKind::Vertex),
("weather.frag", shaderc::ShaderKind::Fragment),
("particle.vert", shaderc::ShaderKind::Vertex),
("particle.frag", shaderc::ShaderKind::Fragment),
("clouds.vert", shaderc::ShaderKind::Vertex),
("clouds.frag", shaderc::ShaderKind::Fragment),
];
Expand Down
56 changes: 49 additions & 7 deletions pomme-client/src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ impl AppCore {
uuid,
entity_type,
position,
velocity,
y_rot_deg,
x_rot_deg,
head_y_rot_deg,
Expand All @@ -733,12 +734,18 @@ impl AppCore {
}
}
if entity_type == azalea_registry::builtin::EntityKind::Item {
game.item_entity_store.spawn_item(id, position);
game.item_entity_store.spawn_item(id, position, velocity);
}
}
NetworkEvent::EntityMoved { id, dx, dy, dz } => {
NetworkEvent::EntityMoved {
id,
dx,
dy,
dz,
on_ground,
} => {
game.entity_store.move_living_delta(id, dx, dy, dz);
game.item_entity_store.move_delta(id, dx, dy, dz);
game.item_entity_store.move_delta(id, dx, dy, dz, on_ground);
}
NetworkEvent::EntityMovedRotated {
id,
Expand All @@ -747,22 +754,53 @@ impl AppCore {
dz,
y_rot_deg,
x_rot_deg,
on_ground,
} => {
game.entity_store.move_living_delta(id, dx, dy, dz);
game.entity_store
.update_living_rotation(id, y_rot_deg, x_rot_deg);
game.item_entity_store.move_delta(id, dx, dy, dz);
game.item_entity_store.move_delta(id, dx, dy, dz, on_ground);
}
NetworkEvent::EntityMotion { id, velocity } => {
game.item_entity_store.set_motion(id, velocity);
}
NetworkEvent::EntityTeleported {
id,
position,
velocity,
y_rot_deg,
x_rot_deg,
on_ground,
} => {
game.entity_store.teleport_living(id, position);
game.entity_store
.update_living_rotation(id, y_rot_deg, x_rot_deg);
game.item_entity_store.teleport(id, position);
game.item_entity_store
.teleport(id, position, velocity, on_ground);
}
NetworkEvent::LevelEvent {
event_type,
pos,
data,
} => {
// Vanilla `LevelEventHandler` case 2001 (block break).
// The server excludes the breaking player from the
// broadcast; the local break's effects come from
// `predict_destroy`. TODO: the other level events.
if event_type == 2001
&& let Ok(state) = azalea_block::BlockState::try_from(data)
{
if !state.is_air() {
crate::player::interaction::play_break_sound(&self.audio, state, pos);
}
game.particle_store.add_destroy_block_effect(
pos,
state,
renderer.registry(),
&game.chunk_store,
&game.biome_climate,
);
}
}
NetworkEvent::EntitiesRemoved { ids } => {
for id in &ids {
Expand Down Expand Up @@ -1092,6 +1130,11 @@ impl AppCore {
game.player.game_mode == 1,
input.selected_slot(),
place_block,
&mut crate::player::interaction::BreakEffects {
particles: &mut game.particle_store,
registry: renderer.registry(),
biome_climate: &game.biome_climate,
},
);
if !dirty.is_empty() {
let min_y = game.chunk_store.min_y();
Expand All @@ -1100,9 +1143,8 @@ impl AppCore {
for b in dirty {
dirty_sections_for_block(&mut sections, b.x, b.y, b.z, min_y, n);
}
// Player edits are always adjacent (lod 0).
for (col, si) in sections {
game.enqueue_section_edit(col, si, 0);
game.mesh_section_edit_now(renderer, col, si);
}
}

Expand Down
107 changes: 75 additions & 32 deletions pomme-client/src/app/phases/in_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::net::connection::ConnectionHandle;
use crate::player::LocalPlayer;
use crate::player::interaction::{HitResult, InteractionState};
use crate::player::tab_list::TabList;
use crate::renderer::chunk::mesher::{BiomeClimate, MeshDispatcher};
use crate::renderer::chunk::mesher::{BiomeClimate, ChunkMeshData, MeshDispatcher};
use crate::renderer::chunk::occlusion_graph::{self, VisibilitySet};
use crate::renderer::pipelines::entity_renderer::{
EntityRenderInfo, WHITE_TINT, jeb_sheep_tint, wool_color_tint,
Expand Down Expand Up @@ -89,6 +89,7 @@ pub struct GameState {
pub server_render_distance: u32,
pub server_simulation_distance: u32,
pub item_entity_store: ItemEntityStore,
pub particle_store: crate::particle::ParticleStore,
pub block_entity_anim: BlockEntityAnimStore,
pub benchmark: Option<Benchmark>,
pub benchmark_result: Option<BenchmarkResult>,
Expand Down Expand Up @@ -172,6 +173,15 @@ impl GameState {
server_render_distance: 0,
server_simulation_distance: 0,
item_entity_store: ItemEntityStore::new(),
particle_store: {
let (grass, foliage, dry_foliage) = mesh_dispatcher.colormaps();
crate::particle::ParticleStore::new(
renderer.atlas_uv_map().clone(),
grass,
foliage,
dry_foliage,
)
},
block_entity_anim: BlockEntityAnimStore::default(),
player: LocalPlayer::new(),
biome_climate: Arc::new(HashMap::new()),
Expand Down Expand Up @@ -298,13 +308,56 @@ impl GameState {
/// visibility. Bumps that section's generation so the result is dropped
/// only if the same section is edited again before it lands.
pub fn enqueue_section_edit(&mut self, col: ChunkPos, si: i32, lod: u32) {
self.next_section_gen += 1;
let g = self.next_section_gen;
self.section_gen.insert((col, si), g);
let g = self.bump_section_gen(col, si);
self.mesh_dispatcher
.enqueue(&self.chunk_store, col, lod, true, g, si..si + 1);
}

/// Vanilla `compileSync` under `PrioritizeChunkUpdates.PLAYER_AFFECTED`:
/// mesh and upload a player-edited section on the spot so the edit shows
/// the same frame. (Vanilla defaults to NONE/async, but pomme's async
/// round-trip is several frames, which leaves a broken block visibly
/// lingering after its crack overlay completes.)
pub fn mesh_section_edit_now(&mut self, renderer: &mut Renderer, col: ChunkPos, si: i32) {
// The gen bump also invalidates any in-flight async result for this
// section, so it can't clobber the sync upload at drain time.
let g = self.bump_section_gen(col, si);
let mesh = self
.mesh_dispatcher
.mesh_section_now(&self.chunk_store, col, si, g);
self.apply_mesh_upload(renderer, mesh);
}

fn bump_section_gen(&mut self, col: ChunkPos, si: i32) -> u64 {
self.next_section_gen += 1;
self.section_gen.insert((col, si), self.next_section_gen);
self.next_section_gen
}

/// Upload a finished mesh and apply its bookkeeping: re-arm the rescan
/// for sections dropped on pool exhaustion, and adopt the per-section
/// visibility sets.
fn apply_mesh_upload(&mut self, renderer: &mut Renderer, mesh: ChunkMeshData) {
let dropped = renderer.upload_chunk_mesh(&mesh);
let pos = mesh.pos;
// Sections dropped on pool exhaustion were retired from the buffer;
// clear their meshed bit so the next rescan re-enqueues them.
if !dropped.is_empty()
&& let Some(m) = self.meshed.get_mut(&pos)
{
for si in dropped {
m.mask &= !(1u32 << si);
}
}
for (si, vis) in mesh.visibility {
let e = self.section_vis_epoch.entry((pos, si)).or_insert(0);
if mesh.upload_epoch >= *e {
*e = mesh.upload_epoch;
self.section_vis.insert((pos, si), vis);
}
}
}

/// Drive the cave-cull occlusion walk: apply a finished async walk to the
/// per-column draw masks, then schedule the next one on 8-block camera
/// movement or chunk loads (one at a time, off the main thread — vanilla's
Expand Down Expand Up @@ -711,7 +764,8 @@ pub fn update_game(
return GameUpdateResult::Disconnected { reason };
}

for mesh in game.mesh_dispatcher.drain_results() {
let meshes: Vec<_> = game.mesh_dispatcher.drain_results().collect();
for mesh in meshes {
// Drop a mesh built from an out-of-date snapshot. Edits (priority lane,
// single section) are keyed per section so editing one section never
// drops a sibling's in-flight result; bulk loads keep the column key.
Expand All @@ -737,24 +791,7 @@ pub fn update_game(
ms(t.enqueued_at.elapsed()),
);
}
let dropped = gfx.renderer.upload_chunk_mesh(&mesh);
let pos = mesh.pos;
// Sections dropped on pool exhaustion were retired from the buffer; clear
// their meshed bit so the next rescan re-enqueues them.
if !dropped.is_empty()
&& let Some(m) = game.meshed.get_mut(&pos)
{
for si in dropped {
m.mask &= !(1u32 << si);
}
}
for (si, vis) in mesh.visibility {
let e = game.section_vis_epoch.entry((pos, si)).or_insert(0);
if mesh.upload_epoch >= *e {
*e = mesh.upload_epoch;
game.section_vis.insert((pos, si), vis);
}
}
game.apply_mesh_upload(&mut gfx.renderer, mesh);
}

game.mesh_dispatcher
Expand All @@ -777,7 +814,8 @@ pub fn update_game(
core.tick_accumulator += dt;
while core.tick_accumulator >= TICK_RATE {
core.tick_physics(&mut gfx.renderer, connection, game);
game.item_entity_store.tick();
game.item_entity_store.tick(&game.chunk_store);
game.particle_store.tick(&game.chunk_store);
game.block_entity_anim.tick();
core.tick_accumulator -= TICK_RATE;
}
Expand Down Expand Up @@ -1458,6 +1496,12 @@ pub fn update_game(
)
};

let particle_quads = if benchmark_running {
Vec::new()
} else {
game.particle_store.extract(partial_tick)
};

let effective_rd = if game.server_render_distance > 0 {
core.menu.render_distance.min(game.server_render_distance)
} else {
Expand Down Expand Up @@ -1493,6 +1537,7 @@ pub fn update_game(
&entity_renders,
&item_renders,
&block_entity_renders,
&particle_quads,
&weather_columns,
if benchmark_running {
crate::renderer::CloudMode::Off
Expand Down Expand Up @@ -1637,14 +1682,12 @@ fn stack_render_count(count: i32) -> usize {
}

fn get_entity_light(chunk_store: &ChunkStore, pos: Position) -> f32 {
use crate::renderer::chunk::mesher::LIGHT_TABLE;
let bx = pos.x.floor() as i32;
let by = pos.y.floor() as i32;
let bz = pos.z.floor() as i32;
let level = chunk_store
.get_sky_light(bx, by, bz)
.max(chunk_store.get_block_light(bx, by, bz));
LIGHT_TABLE[level as usize]
crate::renderer::chunk::mesher::world_brightness(
chunk_store,
pos.x.floor() as i32,
pos.y.floor() as i32,
pos.z.floor() as i32,
)
}

/// Builds the rain/snow columns in a square around the camera (vanilla
Expand Down
Loading
Loading