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
21 changes: 17 additions & 4 deletions pomme-client/src/app/phases/in_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,9 +436,13 @@ pub fn update_game(
if game.options_from_game {
let menu_input = core.build_menu_input();
let r = &gfx.renderer;
let result = core
.menu
.build(sw, sh, &menu_input, |t, s| r.menu_text_width(t, s));
let result = core.menu.build(
sw,
sh,
&menu_input,
|t, s| r.menu_text_width(t, s),
&|name| r.block_textures(name),
);
elements.extend(result.elements);
core.input.clear_just_pressed_actions();
} else if game.dead {
Expand Down Expand Up @@ -755,6 +759,12 @@ pub fn update_game(
game.paused = false;
core.apply_cursor_grab(&gfx.window, Some(game));
}
PauseAction::TextureEditor => {
core.menu.open_texture_editor();
game.options_from_game = true;
game.paused = false;
core.apply_cursor_grab(&gfx.window, Some(game));
}
PauseAction::Disconnect => {
return GameUpdateResult::ManualDisconnect;
}
Expand All @@ -776,7 +786,10 @@ pub fn update_game(
if core.menu.render_distance != game.last_render_distance {
game.sync_render_distance(connection, core.menu.render_distance);
}
if !core.menu.is_options_screen() && !core.menu.is_editor_screen() {
if !core.menu.is_options_screen()
&& !core.menu.is_editor_screen()
&& !core.menu.is_texture_editor_screen()
{
game.options_from_game = false;
game.paused = true;
core.apply_cursor_grab(&gfx.window, Some(game));
Expand Down
10 changes: 7 additions & 3 deletions pomme-client/src/app/phases/in_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ pub fn update_menu(

let menu_input = core.build_menu_input();

let result = core.menu.build(sw, sh, &menu_input, |t, s| {
gfx.renderer.menu_text_width(t, s)
});
let result = core.menu.build(
sw,
sh,
&menu_input,
|t, s| gfx.renderer.menu_text_width(t, s),
&|name| gfx.renderer.block_textures(name),
);
core.audio.set_volumes(core.menu.category_volumes());
let action = result.action;

Expand Down
47 changes: 31 additions & 16 deletions pomme-client/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ pub struct RenderTimings {
pub present_ms: f32,
}

/// One of a block's textures for the texture editor: (face label, texture key,
/// atlas UV region `[u0, v0, u1, v1]`).
pub type BlockTextureRef = (String, String, [f32; 4]);

pub struct Renderer {
ctx: VulkanContext,
swapchain: Swapchain,
Expand Down Expand Up @@ -201,6 +205,7 @@ impl Renderer {
&texture_names,
None,
)?;
menu_pipeline.set_block_atlas(&ctx.device, atlas.view, atlas.sampler);

splash(&mut menu_pipeline, 0.5, "Creating pipelines...");

Expand Down Expand Up @@ -530,7 +535,7 @@ impl Renderer {
cmd.set_scissor(0, &[scissor]);

let empty_uvs: HashMap<String, [f32; 4]> = HashMap::new();
menu.draw(cmd, sw, sh, &elements, &empty_uvs);
menu.draw(0, cmd, sw, sh, &elements, &empty_uvs);

cmd.end_render_pass();
cmd.end()?;
Expand Down Expand Up @@ -888,6 +893,19 @@ impl Renderer {
)
}

/// Labeled textures making up a block's appearance, with each one's UV
/// region in the block atlas, for the texture editor. Empty for non-blocks.
pub fn block_textures(&self, item_name: &str) -> Vec<BlockTextureRef> {
self.registry
.block_face_textures(item_name)
.into_iter()
.map(|(label, key)| {
let r = self.atlas.uv_map.get_region(&key);
(label, key, [r.u_min, r.v_min, r.u_max, r.v_max])
})
.collect()
}

pub fn reload_assets(
&mut self,
game_dir: &Path,
Expand Down Expand Up @@ -927,6 +945,8 @@ impl Renderer {
.rebind_atlas(&self.ctx.device, &self.atlas);
self.held_item_pipeline
.rebind_atlas(&self.ctx.device, &self.atlas);
self.menu_pipeline
.set_block_atlas(&self.ctx.device, self.atlas.view, self.atlas.sampler);

tracing::info!("Assets reloaded");
}
Expand Down Expand Up @@ -1167,30 +1187,25 @@ impl Renderer {
slot: pipelines::gui_item_atlas::Slot,
name: String,
is_block: bool,
needs_clear: bool,
}
// Re-bake all visible icons every frame; the bake pass clears the whole
// atlas, so nothing carries over between frames.
let mut bake_list: Vec<BakeJob> = Vec::new();
for name in &unique_names {
let discard = pipelines::gui_item_atlas::is_animated_item(name);
if let Some((slot, state)) = self.gui_item_atlas.get_or_allocate(name, discard) {
if let Some((slot, _state)) = self.gui_item_atlas.get_or_allocate(name, discard) {
item_atlas_uvs.insert(name.clone(), self.gui_item_atlas.slot_uv(&slot));
if !matches!(state, pipelines::gui_item_atlas::SlotState::Ready) {
bake_list.push(BakeJob {
slot,
name: name.clone(),
is_block: self.registry.get_item_model(name).is_some(),
needs_clear: matches!(state, pipelines::gui_item_atlas::SlotState::Stale),
});
}
bake_list.push(BakeJob {
slot,
name: name.clone(),
is_block: self.registry.get_item_model(name).is_some(),
});
}
}
if !bake_list.is_empty() {
self.gui_item_atlas.begin_bake_pass(cmd);
self.gui_item_pipeline.bind_for_bake_pass(cmd);
for job in &bake_list {
if job.needs_clear {
self.gui_item_atlas.clear_slot_color(cmd, &job.slot);
}
cmd.set_scissor(0, &[self.gui_item_atlas.scissor_rect(&job.slot)]);
let (sx, sy) = self.gui_item_atlas.slot_origin_pixels(&job.slot);
self.gui_item_pipeline.bake_to_slot(
Expand Down Expand Up @@ -1342,7 +1357,7 @@ impl Renderer {
}

self.menu_pipeline
.draw(cmd, sw, sh, overlay, &item_atlas_uvs);
.draw(frame, cmd, sw, sh, overlay, &item_atlas_uvs);

if let Some(p) = player_preview {
let x0 = p.rect[0].max(0.0) as i32;
Expand Down Expand Up @@ -1430,7 +1445,7 @@ impl Renderer {
}

self.menu_pipeline
.draw(cmd, sw, sh, elements, &item_atlas_uvs);
.draw(frame, cmd, sw, sh, elements, &item_atlas_uvs);
}
}

Expand Down
23 changes: 4 additions & 19 deletions pomme-client/src/renderer/pipelines/gui_item_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,24 +336,6 @@ impl GuiItemAtlas {
cmd.end_render_pass();
}

pub fn clear_slot_color(&self, cmd: vk::CommandBuffer, slot: &Slot) {
let clear_attachment = vk::ClearAttachment {
aspect_mask: vk::ImageAspectFlags::Color,
color_attachment: 0,
clear_value: vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 0.0],
},
},
};
let clear_rect = vk::ClearRect {
rect: self.scissor_rect(slot),
base_array_layer: 0,
layer_count: 1,
};
cmd.clear_attachments(&[clear_attachment], &[clear_rect]);
}

pub fn color_view(&self) -> vk::ImageView {
self.color_view
}
Expand Down Expand Up @@ -563,7 +545,10 @@ fn create_render_pass(device: &vk::Device) -> vk::RenderPass {
vk::AttachmentDescription {
format: COLOR_FORMAT,
samples: vk::SampleCountFlags::Type1,
load_op: vk::AttachmentLoadOp::Load,
// Clear (not Load): icons are re-baked every frame, so prior atlas
// contents are never relied on — sidesteps MoltenVK dropping
// LoadOp::Load / clear_attachments content.
load_op: vk::AttachmentLoadOp::Clear,
store_op: vk::AttachmentStoreOp::Store,
initial_layout: vk::ImageLayout::ShaderReadOnlyOptimal,
final_layout: vk::ImageLayout::ShaderReadOnlyOptimal,
Expand Down
85 changes: 80 additions & 5 deletions pomme-client/src/renderer/pipelines/menu_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use pomme_gpu_allocator::vulkan::{Allocation, Allocator};
use pyronyx::vk;

use crate::assets::{AssetIndex, resolve_asset_path};
use crate::renderer::{shader, util};
use crate::renderer::{MAX_FRAMES_IN_FLIGHT, shader, util};
use crate::ui::font::GlyphMap;
use crate::ui::text::TextSpan;

Expand All @@ -24,6 +24,8 @@ pub const ICON_GLOBE: char = '\u{f0ac}';
pub const ICON_COMMENT: char = '\u{f075}';
pub const ICON_CODE: char = '\u{f121}';
pub const ICON_CHECK: char = '\u{f00c}';
pub const ICON_TRASH: char = '\u{f1f8}';
pub const ICON_CUBES: char = '\u{f1b3}';

#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
Expand All @@ -38,6 +40,8 @@ struct Vertex {

const MAX_VERTICES: usize = 16384;
const VERTEX_SIZE: usize = size_of::<Vertex>();
/// Per-in-flight-frame slice of the vertex buffer.
const VERTEX_REGION_BYTES: usize = MAX_VERTICES * VERTEX_SIZE;

struct DrawOp {
start: u32,
Expand Down Expand Up @@ -81,6 +85,8 @@ fn build_font_atlas() -> FontAtlas {
ICON_COMMENT,
ICON_CODE,
ICON_CHECK,
ICON_TRASH,
ICON_CUBES,
]
.iter()
.map(|&ch| (ch, &icon_font))
Expand Down Expand Up @@ -236,6 +242,13 @@ impl MenuOverlayPipeline {
stage_flags: vk::ShaderStageFlags::Fragment,
..Default::default()
},
vk::DescriptorSetLayoutBinding {
binding: 6,
descriptor_type: vk::DescriptorType::CombinedImageSampler,
descriptor_count: 1,
stage_flags: vk::ShaderStageFlags::Fragment,
..Default::default()
},
];
let tex_layout_info = vk::DescriptorSetLayoutCreateInfo {
binding_count: tex_bindings.len() as u32,
Expand Down Expand Up @@ -265,7 +278,7 @@ impl MenuOverlayPipeline {
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::CombinedImageSampler,
descriptor_count: 6,
descriptor_count: 7,
},
];
let pool_info = vk::DescriptorPoolCreateInfo {
Expand Down Expand Up @@ -517,13 +530,23 @@ impl MenuOverlayPipeline {
image_info: &favicon_img_info,
..Default::default()
},
vk::WriteDescriptorSet {
dst_set: tex_set,
dst_binding: 6,
descriptor_count: 1,
descriptor_type: vk::DescriptorType::CombinedImageSampler,
image_info: &item_img_info,
..Default::default()
},
];
device.update_descriptor_sets(&writes, &[]);

// One region per in-flight frame so a frame being recorded never
// overwrites vertices another in-flight frame is still reading.
let (vertex_buffer, vertex_allocation) = util::create_host_buffer(
device,
allocator,
(MAX_VERTICES * VERTEX_SIZE) as u64,
(VERTEX_REGION_BYTES * MAX_FRAMES_IN_FLIGHT) as u64,
vk::BufferUsageFlags::VertexBuffer,
"menu_vertices",
);
Expand Down Expand Up @@ -573,6 +596,7 @@ impl MenuOverlayPipeline {

pub fn draw(
&mut self,
frame: usize,
cmd: vk::CommandBuffer,
screen_w: f32,
screen_h: f32,
Expand Down Expand Up @@ -871,6 +895,30 @@ impl MenuOverlayPipeline {
);
}
}
MenuElement::AtlasTexture {
x,
y,
w,
h,
region,
tint,
} => {
push_quad(
&mut vertices,
*x,
*y,
*w,
*h,
region[0],
region[1],
region[2],
region[3],
*tint,
7.0,
[*w, *h],
0.0,
);
}
_ => {}
}
}
Expand Down Expand Up @@ -1051,14 +1099,15 @@ impl MenuOverlayPipeline {
return;
}

let region_offset = frame * VERTEX_REGION_BYTES;
if !vertices.is_empty() {
let count = vertices.len().min(MAX_VERTICES);
let byte_data = bytemuck::cast_slice(&vertices[..count]);
self.vertex_allocation
.as_mut()
.unwrap()
.mapped_slice_mut()
.unwrap()[..byte_data.len()]
.unwrap()[region_offset..region_offset + byte_data.len()]
.copy_from_slice(byte_data);
}

Expand All @@ -1079,7 +1128,7 @@ impl MenuOverlayPipeline {
&[self.globals_set, self.tex_set],
&[],
);
cmd.bind_vertex_buffers(0, &[self.vertex_buffer], &[0]);
cmd.bind_vertex_buffers(0, &[self.vertex_buffer], &[region_offset as u64]);
}
for op in &draw_ops {
let rect = if let Some(s) = op.scissor {
Expand Down Expand Up @@ -1119,6 +1168,23 @@ impl MenuOverlayPipeline {
device.update_descriptor_sets(&[write], &[]);
}

pub fn set_block_atlas(&self, device: &vk::Device, view: vk::ImageView, sampler: vk::Sampler) {
let info = vk::DescriptorImageInfo {
sampler,
image_view: view,
image_layout: vk::ImageLayout::ShaderReadOnlyOptimal,
};
let write = vk::WriteDescriptorSet {
dst_set: self.tex_set,
dst_binding: 6,
descriptor_count: 1,
descriptor_type: vk::DescriptorType::CombinedImageSampler,
image_info: &info,
..Default::default()
};
device.update_descriptor_sets(&[write], &[]);
}

pub fn set_blur_texture(&self, device: &vk::Device, view: vk::ImageView, sampler: vk::Sampler) {
let info = vk::DescriptorImageInfo {
sampler,
Expand Down Expand Up @@ -1452,6 +1518,15 @@ pub enum MenuElement {
size: f32,
address: String,
},
/// A texture drawn straight from the block atlas, by its UV region.
AtlasTexture {
x: f32,
y: f32,
w: f32,
h: f32,
region: [f32; 4],
tint: [f32; 4],
},
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
Expand Down
Loading