gfx: upload atlas patches on skipped frames and refresh skins after pack reload

Fixes #5097

Co-Authored-By: Antigravity <[email protected]>
This commit is contained in:
mularealandAntigravity committed 2026-10-31 18:00:31 +01:00
1 parent 571ec1b76e
commit 190e96938b
6 files changed
+75 -33

No files matched your search

+20 -2
View File
@@ -4044,8 +4044,26 @@ impl Renderer {
}
pub fn render(&mut self, frame: &Frame) -> Result<bool> {
let rendered = self.draw_frame(frame);
// A frame that was not drawn still submits the section copies.
if let Some(copies) = self.take_section_copies() {
// A frame that was not drawn still submits the section copies and
// atlas patches (glyphs, skins, maps), which the caller already took.
if !matches!(rendered, Ok(true)) && !frame.hud.patches.is_empty() {
let section_copies = self.section_copies.take();
let has_section_copies = section_copies.is_some();
let mut encoder = section_copies.unwrap_or_else(|| {
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Horizon atlas patches"),
})
});
self.hud
.upload_patches(&mut self.uploads, &mut encoder, &frame.hud.patches);
self.uploads.finish(&encoder);
if has_section_copies {
self.section_copy_bytes = 0;
self.section_uploads.finish(&encoder);
}
self.queue.submit([encoder.finish()]);
} else if let Some(copies) = self.take_section_copies() {
self.queue.submit([copies]);
}
rendered
+33 -20
View File
@@ -1817,6 +1817,38 @@ impl HudRenderer {
*previous = state;
}
}
/// Stage `patches` into the shared atlas texture.
pub fn upload_patches(
&self,
uploads: &mut crate::gfx::upload::Uploads,
encoder: &mut wgpu::CommandEncoder,
patches: &[crate::font::Patch],
) {
if let Some(texture) = &self.atlas_texture {
for p in patches {
if p.x.saturating_add(p.width) <= texture.width()
&& p.y.saturating_add(p.height) <= texture.height()
{
uploads.texture(
encoder,
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: p.x,
y: p.y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
p.width,
p.height,
&p.rgba,
);
}
}
}
}
/// Stage the batch's atlas patches, vertices and uniform: before the
/// frame's first pass, so the copies share one command buffer.
pub fn prepare(
@@ -1827,26 +1859,7 @@ impl HudRenderer {
batch: &HudBatch,
size: [u32; 2],
) {
if let Some(texture) = &self.atlas_texture {
for p in &batch.patches {
uploads.texture(
encoder,
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: p.x,
y: p.y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
p.width,
p.height,
&p.rgba,
);
}
}
self.upload_patches(uploads, encoder, &batch.patches);
if batch.vertices.is_empty() || size.contains(&0) {
return;
}
+4
View File
@@ -7276,6 +7276,10 @@ impl Game<'_> {
return batch;
}
if self.screen == Screen::Options {
if self.title.is_none() {
self.flush_skin_slots();
self.update_remote_skins();
}
self.draw_options(&mut batch);
return batch;
}
+1
View File
@@ -301,6 +301,7 @@ impl Game<'_> {
// upload into it again instead of naming sprites it does not have.
self.skin_slots.forget();
self.maps.slots.forget();
self.map_patches.clear();
// The atlas's sprites moved: the particle engine re-reads every rect
// and the packs' sprite sets, or every particle draws a stale region.
self.particles
+5 -4
View File
@@ -4348,7 +4348,7 @@ impl Game<'_> {
/// `PlayerInfo.getSkin` for every player entity, including NPCs that are
/// never in the tab list: resolve each profile's textures and give the
/// level's player models their skin.
fn update_remote_skins(&mut self) {
pub(super) fn update_remote_skins(&mut self) {
let local = self.remote.as_ref().map(|r| r.profile);
self.poll_own_profile();
let Some(r) = self.remote.as_mut() else {
@@ -4413,7 +4413,7 @@ impl Game<'_> {
&& let Some((rgba, is_slim)) = horizon_client::skins::offline_skin()
{
const OFFLINE: &str = "horizon/offline_skin";
if !self.skin_slots.contains(OFFLINE) {
if !self.skin_slots.contains(OFFLINE) || !self.gui.atlas.sprites.contains_key(OFFLINE) {
self.store_skin(OFFLINE.into(), rgba.clone());
}
if *is_slim {
@@ -4426,7 +4426,7 @@ impl Game<'_> {
&& let Some(rgba) = horizon_client::skins::offline_cape()
{
const OFFLINE: &str = "capes/offline";
if !self.skin_slots.contains(OFFLINE) {
if !self.skin_slots.contains(OFFLINE) || !self.gui.atlas.sprites.contains_key(OFFLINE) {
self.store_skin(OFFLINE.into(), rgba.to_vec());
}
capes.insert(local, OFFLINE.into());
@@ -4610,7 +4610,8 @@ impl Game<'_> {
uuid: u128,
(skin, slim): (String, bool),
) -> (String, bool) {
if skin.starts_with("skins/") && !self.again_skin(&skin) {
if (skin.starts_with("skins/") || skin == "horizon/offline_skin") && !self.again_skin(&skin)
{
let skin = horizon_client::tab_list::default_skin(uuid);
return (skin.to_owned(), skin.contains("/slim/"));
}
+12 -7
View File
@@ -747,13 +747,9 @@ impl Game<'_> {
}
impl Game<'_> {
/// `Hud.extractTabList`: the player list while the player-list key is
/// held. A local world shows it with a joined LAN guest or a `list`
/// objective.
pub(super) fn draw_tab_list(&mut self, batch: &mut HudBatch) {
use horizon_client::tab_list::{self, TabInputs};
// Player heads and the player list both draw from the same skin
// slots: a new frame, with what the last one drew.
/// Advance the skin slots to a new frame with what the last one drew, and
/// store any finished skin downloads into the atlas.
pub(super) fn flush_skin_slots(&mut self) {
let (ready, drawn) = {
let mut downloads = self.skin_downloads.borrow_mut();
(downloads.take_ready(), downloads.take_drawn())
@@ -762,6 +758,15 @@ impl Game<'_> {
for (location, rgba) in ready {
self.store_skin(location, rgba);
}
}
/// `Hud.extractTabList`: the player list while the player-list key is
/// held. A local world shows it with a joined LAN guest or a `list`
/// objective.
pub(super) fn draw_tab_list(&mut self, batch: &mut HudBatch) {
use horizon_client::tab_list::{self, TabInputs};
// Player heads and the player list both draw from the same skin
// slots: a new frame, with what the last one drew.
self.flush_skin_slots();
let key = self.screen == super::Screen::Playing
&& self.keys.contains(&winit::keyboard::KeyCode::Tab);
let (mut players, online_mode, upside_down) = match self.remote_tab_players() {