Skip to content

Commit 28d55f7

Browse files
committed
Texture sorting and filtering
1 parent a36b4f9 commit 28d55f7

File tree

3 files changed

+243
-10
lines changed

3 files changed

+243
-10
lines changed

src/gui/common.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub trait ResponseExt {
2222
}
2323

2424
impl ResponseExt for egui::Response {
25+
fn tag_context(&self, tag: TagHash, tag64: Option<TagHash64>) -> &Self {
26+
self.context_menu(|ui| tag_context(ui, tag, tag64));
27+
self
28+
}
29+
2530
fn tag_context_with_texture(
2631
self,
2732
tag: TagHash,
@@ -38,11 +43,6 @@ impl ResponseExt for egui::Response {
3843
self
3944
}
4045
}
41-
42-
fn tag_context(&self, tag: TagHash, tag64: Option<TagHash64>) -> &Self {
43-
self.context_menu(|ui| tag_context(ui, tag, tag64));
44-
self
45-
}
4646
}
4747

4848
pub fn tag_context(ui: &mut egui::Ui, tag: TagHash, tag64: Option<TagHash64>) {

src/gui/texture.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use eframe::epaint::mutex::RwLock;
1010
use eframe::epaint::{vec2, TextureId};
1111
use eframe::wgpu;
1212
use eframe::wgpu::util::DeviceExt;
13-
use eframe::wgpu::TextureDimension;
13+
use eframe::wgpu::{TextureDescriptor, TextureDimension, TextureFormat};
1414
use either::Either::{self, Left};
1515
use image::GenericImageView;
1616
use linked_hash_map::LinkedHashMap;
@@ -89,13 +89,22 @@ pub struct Texture {
8989
pub comment: Option<String>,
9090
}
9191

92-
struct TextureDesc {
92+
pub struct TextureDesc {
9393
pub format: wgpu::TextureFormat,
9494
pub width: u32,
9595
pub height: u32,
9696
pub depth: u32,
9797
}
9898

99+
impl TextureDesc {
100+
pub fn info(&self) -> String {
101+
format!(
102+
"{}x{}x{} {:?}",
103+
self.width, self.height, self.depth, self.format
104+
)
105+
}
106+
}
107+
99108
impl Texture {
100109
pub fn load_data_d2(
101110
hash: TagHash,
@@ -198,6 +207,51 @@ impl Texture {
198207
}
199208
}
200209

210+
pub fn load_desc(hash: TagHash) -> anyhow::Result<TextureDesc> {
211+
if package_manager().version.is_d1() && package_manager().platform != PackagePlatform::PS4 {
212+
anyhow::bail!("Textures are not supported for D1");
213+
}
214+
215+
match package_manager().version {
216+
destiny_pkg::PackageVersion::DestinyInternalAlpha
217+
| destiny_pkg::PackageVersion::DestinyTheTakenKing => todo!(),
218+
destiny_pkg::PackageVersion::DestinyRiseOfIron => {
219+
let texture: TextureHeaderRoiPs4 = package_manager().read_tag_binrw(hash)?;
220+
Ok(TextureDesc {
221+
format: texture.format.to_wgpu()?,
222+
width: texture.width as u32,
223+
height: texture.height as u32,
224+
depth: texture.depth as u32,
225+
})
226+
}
227+
destiny_pkg::PackageVersion::Destiny2Beta
228+
| destiny_pkg::PackageVersion::Destiny2Shadowkeep
229+
| destiny_pkg::PackageVersion::Destiny2BeyondLight
230+
| destiny_pkg::PackageVersion::Destiny2WitchQueen
231+
| destiny_pkg::PackageVersion::Destiny2Lightfall => {
232+
let header_data = package_manager()
233+
.read_tag(hash)
234+
.context("Failed to read texture header")?;
235+
236+
let is_prebl = matches!(
237+
package_manager().version,
238+
destiny_pkg::PackageVersion::Destiny2Beta
239+
| destiny_pkg::PackageVersion::Destiny2Shadowkeep
240+
);
241+
242+
let mut cur = std::io::Cursor::new(header_data);
243+
let texture: TextureHeader = cur.read_le_args((is_prebl,))?;
244+
245+
Ok(TextureDesc {
246+
format: texture.format.to_wgpu()?,
247+
width: texture.width as u32,
248+
height: texture.height as u32,
249+
depth: texture.depth as u32,
250+
})
251+
}
252+
}
253+
}
254+
201255
pub fn load(rs: &RenderState, hash: TagHash) -> anyhow::Result<Texture> {
202256
if package_manager().version.is_d1() && package_manager().platform != PackagePlatform::PS4 {
203257
anyhow::bail!("Textures are not supported for D1");
@@ -325,6 +379,27 @@ impl Texture {
325379
None,
326380
)
327381
}
382+
383+
// fn to_rgba(&self, rs: &RenderState) -> anyhow::Result<Vec<u8>> {
384+
// let mut encoder = rs
385+
// .device
386+
// .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
387+
//
388+
// let rgba_buffer = rs.device.create_texture(&TextureDescriptor {
389+
// label: None,
390+
// size: Default::default(),
391+
// mip_level_count: 0,
392+
// sample_count: 0,
393+
// dimension: TextureDimension::D2,
394+
// format: TextureFormat::Rgba8UnormSrgb,
395+
// usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST,
396+
// view_formats: &[TextureFormat::Rgba8UnormSrgb],
397+
// });
398+
//
399+
// rs.queue.submit([encoder.finish()]);
400+
//
401+
// Ok(vec![])
402+
// }
328403
}
329404

330405
pub type LoadedTexture = (Arc<Texture>, TextureId);
@@ -561,3 +636,20 @@ mod swizzle {
561636
}
562637
}
563638
}
639+
640+
// mod texture_capture {
641+
// fn capture_texture(
642+
// rs: &super::RenderState,
643+
// texture: &super::Texture,
644+
// ) -> anyhow::Result<Vec<u8>> {
645+
// todo!()
646+
// }
647+
//
648+
// /// Capture a texture to a raw RGBA buffer
649+
// pub fn capture_texture(
650+
// rs: &super::RenderState,
651+
// texture: &super::Texture,
652+
// ) -> anyhow::Result<Vec<u8>> {
653+
// todo!()
654+
// }
655+
// }

src/gui/texturelist.rs

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use destiny_pkg::{manager::PackagePath, TagHash};
22
use eframe::egui::{self, pos2, vec2, Color32, RichText, Stroke, Widget};
3+
use rayon::iter::{ParallelBridge, ParallelIterator};
4+
use std::fmt::{Display, Formatter};
35

6+
use crate::gui::texture::{Texture, TextureDesc};
47
use crate::{packages::package_manager, tagtypes::TagType};
58

69
use super::{common::ResponseExt, texture::TextureCache, View, ViewAction};
@@ -10,10 +13,12 @@ pub struct TexturesView {
1013
packages_with_textures: Vec<u16>,
1114
package_filter: String,
1215
texture_cache: TextureCache,
13-
textures: Vec<(usize, TagHash, TagType)>,
16+
textures: Vec<(usize, TagHash, TagType, Option<TextureDesc>)>,
1417

1518
keep_aspect_ratio: bool,
1619
zoom: f32,
20+
sorting: Sorting,
21+
filter_texdesc: String,
1722
}
1823

1924
impl TexturesView {
@@ -26,6 +31,8 @@ impl TexturesView {
2631
textures: vec![],
2732
keep_aspect_ratio: true,
2833
zoom: 1.0,
34+
sorting: Sorting::IndexAsc,
35+
filter_texdesc: String::new(),
2936
}
3037
}
3138

@@ -60,6 +67,23 @@ impl TexturesView {
6067

6168
packages.into_iter().map(|(id, _path)| id).collect()
6269
}
70+
71+
fn apply_sorting(&mut self) {
72+
match self.sorting {
73+
Sorting::IndexAsc | Sorting::IndexDesc => {
74+
self.textures.sort_by_cached_key(|(i, _, _, _)| *i);
75+
}
76+
Sorting::SizeAsc | Sorting::SizeDesc => {
77+
self.textures.sort_by_cached_key(|(_, _, _, desc)| {
78+
desc.as_ref().map(|d| d.width * d.height).unwrap_or(0)
79+
});
80+
}
81+
}
82+
83+
if self.sorting.is_descending() {
84+
self.textures.reverse();
85+
}
86+
}
6387
}
6488

6589
impl View for TexturesView {
@@ -89,6 +113,7 @@ impl View for TexturesView {
89113
egui::ScrollArea::vertical()
90114
.max_width(f32::INFINITY)
91115
.show(ui, |ui| {
116+
let mut update_filters = false;
92117
for id in &self.packages_with_textures {
93118
let path = &package_manager().package_paths[id];
94119
let package_name = format!("{}_{}", path.name, path.id);
@@ -107,15 +132,65 @@ impl View for TexturesView {
107132
.filter_map(|(i, e)| {
108133
let st =
109134
TagType::from_type_subtype(e.file_type, e.file_subtype);
135+
136+
let hash = TagHash::new(*id, i as u16);
110137
if st.is_texture() && st.is_header() {
111-
Some((i, TagHash::new(*id, i as u16), st))
138+
Some((i, hash, st, Texture::load_desc(hash).ok()))
112139
} else {
113140
None
114141
}
115142
})
116143
.collect();
144+
145+
update_filters = true;
117146
}
118147
}
148+
149+
// cohae: Activate at your own risk. May cause death
150+
// if ui
151+
// .selectable_value(
152+
// &mut self.selected_package,
153+
// 0xffff - 1,
154+
// "All Uncompressed".to_string(),
155+
// )
156+
// .changed()
157+
// {
158+
// self.textures = package_manager()
159+
// .package_entry_index
160+
// .iter()
161+
// .par_bridge()
162+
// .flat_map(|(pkg_id, entries)| {
163+
// let mut tex_entries = vec![];
164+
// for (i, e) in entries.iter().enumerate() {
165+
// let hash = TagHash::new(*pkg_id, i as u16);
166+
// let st =
167+
// TagType::from_type_subtype(e.file_type, e.file_subtype);
168+
// if st.is_texture() && st.is_header() {
169+
// let Ok(desc) = Texture::load_desc(hash) else {
170+
// continue;
171+
// };
172+
//
173+
// if !desc.format.is_compressed() {
174+
// tex_entries.push((
175+
// (*pkg_id as usize) * 8192 + i,
176+
// hash,
177+
// st,
178+
// Some(desc),
179+
// ));
180+
// }
181+
// }
182+
// }
183+
//
184+
// tex_entries
185+
// })
186+
// .collect();
187+
//
188+
// update_filters = true;
189+
// }
190+
191+
if update_filters {
192+
self.apply_sorting();
193+
}
119194
});
120195
});
121196

@@ -127,7 +202,36 @@ impl View for TexturesView {
127202
.ui(ui);
128203

129204
ui.checkbox(&mut self.keep_aspect_ratio, "Keep aspect ratio");
205+
206+
if egui::ComboBox::from_label("Sort by")
207+
.selected_text(&self.sorting.to_string())
208+
.show_ui(ui, |ui| {
209+
let mut changed = ui
210+
.selectable_value(&mut self.sorting, Sorting::IndexAsc, "Index ⬆")
211+
.changed();
212+
changed |= ui
213+
.selectable_value(&mut self.sorting, Sorting::IndexDesc, "Index ⬇")
214+
.changed();
215+
changed |= ui
216+
.selectable_value(&mut self.sorting, Sorting::SizeAsc, "Size ⬆")
217+
.changed();
218+
changed |= ui
219+
.selectable_value(&mut self.sorting, Sorting::SizeDesc, "Size ⬇")
220+
.changed();
221+
changed
222+
})
223+
.inner
224+
.unwrap_or(false)
225+
{
226+
self.apply_sorting();
227+
}
228+
});
229+
230+
ui.horizontal(|ui| {
231+
ui.label("Texture desc filter: ");
232+
ui.text_edit_singleline(&mut self.filter_texdesc).changed();
130233
});
234+
131235
ui.separator();
132236
egui::ScrollArea::vertical()
133237
.auto_shrink([false, false])
@@ -145,7 +249,16 @@ impl View for TexturesView {
145249
s.interaction.tooltip_delay = 0.0;
146250
});
147251

148-
for (_i, hash, _tag_type) in &self.textures {
252+
let filter = self.filter_texdesc.to_lowercase();
253+
for (_i, hash, _tag_type, desc) in &self.textures {
254+
if let Some(desc) = desc {
255+
if !filter.is_empty()
256+
&& !desc.info().to_lowercase().contains(&filter)
257+
{
258+
continue;
259+
}
260+
}
261+
149262
let img_container = ui.allocate_response(
150263
vec2(128.0 * self.zoom, 128.0 * self.zoom),
151264
egui::Sense::click(),
@@ -205,6 +318,7 @@ impl View for TexturesView {
205318
&self.texture_cache,
206319
true,
207320
)
321+
.on_hover_text(RichText::new(format!("{hash}")).strong())
208322
.clicked()
209323
{
210324
action = Some(ViewAction::OpenTag(*hash));
@@ -219,3 +333,30 @@ impl View for TexturesView {
219333
action
220334
}
221335
}
336+
337+
#[derive(Default, PartialEq)]
338+
pub enum Sorting {
339+
#[default]
340+
IndexAsc,
341+
IndexDesc,
342+
343+
SizeAsc,
344+
SizeDesc,
345+
}
346+
347+
impl Sorting {
348+
pub fn is_descending(&self) -> bool {
349+
matches!(self, Sorting::IndexDesc | Sorting::SizeDesc)
350+
}
351+
}
352+
353+
impl Display for Sorting {
354+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
355+
match self {
356+
Sorting::IndexAsc => f.write_str("Index ⬆"),
357+
Sorting::IndexDesc => f.write_str("Index ⬇"),
358+
Sorting::SizeAsc => f.write_str("Size ⬆"),
359+
Sorting::SizeDesc => f.write_str("Size ⬇"),
360+
}
361+
}
362+
}

0 commit comments

Comments
 (0)