Skip to main content

nexus\api/
texture.rs

1//! Texture loading.
2
3use crate::{
4    AddonApi, TextureApi,
5    ffi::{path_to_ansi, str_to_c},
6};
7use std::{
8    ffi::{c_char, c_void},
9    mem,
10    path::Path,
11    ptr::NonNull,
12};
13use windows::Win32::{Foundation::HMODULE, Graphics::Direct3D11::ID3D11ShaderResourceView};
14
15/// A loaded texture.
16#[derive(Debug, Clone)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18#[repr(C)]
19pub struct Texture {
20    /// Width of the texture.
21    pub width: u32,
22
23    /// Height of the texture.
24    pub height: u32,
25
26    /// Shader resource view of the texture.
27    #[cfg_attr(feature = "serde", serde(skip))]
28    pub resource: ID3D11ShaderResourceView,
29}
30
31impl Texture {
32    /// Returns the associated resource as raw pointer.
33    #[inline]
34    pub fn resource_ptr(&self) -> *const c_void {
35        // ShaderResourceView is a IUnknown, which is is a NonNull<c_void>
36        unsafe { mem::transmute::<&ID3D11ShaderResourceView, &NonNull<c_void>>(&self.resource) }
37            .as_ptr()
38    }
39
40    /// Returns the associated [`imgui::TextureId`].
41    #[inline]
42    pub fn id(&self) -> imgui::TextureId {
43        self.resource_ptr().into()
44    }
45
46    /// Returns the original texture size in [`imgui`] format.
47    #[inline]
48    pub fn size(&self) -> [f32; 2] {
49        [self.width as f32, self.height as f32]
50    }
51
52    /// Returns a resized texture size in [`imgui`] format.
53    #[inline]
54    pub fn size_resized(&self, factor: f32) -> [f32; 2] {
55        let [x, y] = self.size();
56        [factor * x, factor * y]
57    }
58}
59
60pub type RawTextureReceiveCallback =
61    extern "C-unwind" fn(identifier: *const c_char, texture: *const Texture);
62
63pub type RawTextureGet = unsafe extern "C-unwind" fn(identifier: *const c_char) -> *const Texture;
64
65pub type RawTextureGetOrCreateFromFile = unsafe extern "C-unwind" fn(
66    identifier: *const c_char,
67    filename: *const c_char,
68) -> *const Texture;
69
70pub type RawTextureGetOrCreateFromResource = unsafe extern "C-unwind" fn(
71    identifier: *const c_char,
72    resource_id: u32,
73    module: HMODULE,
74) -> *const Texture;
75
76pub type RawTextureGetOrCreateFromUrl = unsafe extern "C-unwind" fn(
77    identifier: *const c_char,
78    remote: *const c_char,
79    endpoint: *const c_char,
80) -> *const Texture;
81
82pub type RawTextureGetOrCreateFromMemory = unsafe extern "C-unwind" fn(
83    identifier: *const c_char,
84    data: *const c_void,
85    size: usize,
86) -> *const Texture;
87
88pub type RawTextureLoadFromFile = unsafe extern "C-unwind" fn(
89    identifier: *const c_char,
90    filename: *const c_char,
91    callback: RawTextureReceiveCallback,
92);
93
94pub type RawTextureLoadFromResource = unsafe extern "C-unwind" fn(
95    identifier: *const c_char,
96    resource_id: u32,
97    module: HMODULE,
98    callback: RawTextureReceiveCallback,
99);
100
101pub type RawTextureLoadFromUrl = unsafe extern "C-unwind" fn(
102    identifier: *const c_char,
103    remote: *const c_char,
104    endpoint: *const c_char,
105    callback: RawTextureReceiveCallback,
106);
107
108pub type RawTextureLoadFromMemory = unsafe extern "C-unwind" fn(
109    identifier: *const c_char,
110    data: *const c_void,
111    size: usize,
112    callback: RawTextureReceiveCallback,
113);
114
115/// Attempts to retrieve a texture by its identifier.
116pub fn get_texture(identifier: impl AsRef<str>) -> Option<Texture> {
117    let TextureApi { get, .. } = AddonApi::get().texture;
118    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
119    unsafe { get(identifier.as_ptr()).as_ref().cloned() }
120}
121
122/// Attempts to retrieve a texture or creates it from the given file path.
123///
124/// A conversion of the file path may have to be performed every call.
125/// To avoid the conversion when the path is known to be ASCII, use [`get_texture_or_create_from_file_ascii`].
126pub fn get_texture_or_create_from_file(
127    identifier: impl AsRef<str>,
128    file: impl AsRef<Path>,
129) -> Option<Texture> {
130    let TextureApi {
131        get_or_create_from_file,
132        ..
133    } = AddonApi::get().texture;
134    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
135    let file = path_to_ansi(file).expect("failed to convert texture file");
136    unsafe {
137        get_or_create_from_file(identifier.as_ptr(), file.as_ptr())
138            .as_ref()
139            .cloned()
140    }
141}
142
143/// Attempts to retrieve a texture or creates it from the given ASCII file path.
144///
145/// # Safety
146/// The file path must only contain valid ASCII characters.
147pub unsafe fn get_texture_or_create_from_file_ascii(
148    identifier: impl AsRef<str>,
149    file: impl AsRef<str>,
150) -> Option<Texture> {
151    let file = file.as_ref();
152    debug_assert!(file.is_ascii());
153
154    let TextureApi {
155        get_or_create_from_file,
156        ..
157    } = AddonApi::get().texture;
158    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
159    let file = str_to_c(file).expect("failed to convert texture file");
160    unsafe {
161        get_or_create_from_file(identifier.as_ptr(), file.as_ptr())
162            .as_ref()
163            .cloned()
164    }
165}
166
167/// Attempts to retrieve a texture or creates it from the given resource.
168pub fn get_texture_or_create_from_resource(
169    identifier: impl AsRef<str>,
170    resource_id: u32,
171    module: HMODULE,
172) -> Option<Texture> {
173    let TextureApi {
174        get_or_create_from_resource,
175        ..
176    } = AddonApi::get().texture;
177    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
178    unsafe {
179        get_or_create_from_resource(identifier.as_ptr(), resource_id, module)
180            .as_ref()
181            .cloned()
182    }
183}
184
185/// Attempts to retrieve a texture or creates it from the given URL.
186pub fn get_texture_or_create_from_url(
187    identifier: impl AsRef<str>,
188    remote: impl AsRef<str>,
189    endpoint: impl AsRef<str>,
190) -> Option<Texture> {
191    let TextureApi {
192        get_or_create_from_url,
193        ..
194    } = AddonApi::get().texture;
195    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
196    let remote = str_to_c(remote).expect("failed to convert texture url remote");
197    let endpoint = str_to_c(endpoint).expect("failed to convert texture url endpoint");
198    unsafe {
199        get_or_create_from_url(identifier.as_ptr(), remote.as_ptr(), endpoint.as_ptr())
200            .as_ref()
201            .cloned()
202    }
203}
204
205/// Attempts to retrieve a texture or creates it from the given memory.
206pub fn get_texture_or_create_from_memory(
207    identifier: impl AsRef<str>,
208    memory: impl AsRef<[u8]>,
209) -> Option<Texture> {
210    let TextureApi {
211        get_or_create_from_memory,
212        ..
213    } = AddonApi::get().texture;
214    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
215    let memory = memory.as_ref();
216    unsafe {
217        get_or_create_from_memory(identifier.as_ptr(), memory.as_ptr().cast(), memory.len())
218            .as_ref()
219            .cloned()
220    }
221}
222
223/// Loads a texture from the given file path.
224///
225/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
226///
227/// A conversion of the file path may have to be performed every call.
228/// To avoid the conversion when the path is known to be ASCII, use [`load_texture_from_file_ascii`].
229pub fn load_texture_from_file(
230    identifier: impl AsRef<str>,
231    file: impl AsRef<Path>,
232    callback: Option<RawTextureReceiveCallback>,
233) {
234    let TextureApi { load_from_file, .. } = AddonApi::get().texture;
235    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
236    let file = path_to_ansi(file).expect("failed to convert texture file");
237    unsafe {
238        load_from_file(
239            identifier.as_ptr(),
240            file.as_ptr(),
241            callback.unwrap_or(dummy_receive_texture),
242        )
243    }
244}
245
246/// Loads a texture from the given file path.
247///
248/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
249///
250/// # Safety
251/// The file path must only contain valid ASCII characters.
252pub unsafe fn load_texture_from_file_ascii(
253    identifier: impl AsRef<str>,
254    file: impl AsRef<str>,
255    callback: Option<RawTextureReceiveCallback>,
256) {
257    let file = file.as_ref();
258    debug_assert!(file.is_ascii());
259
260    let TextureApi { load_from_file, .. } = AddonApi::get().texture;
261    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
262    let file = str_to_c(file).expect("failed to convert texture file");
263    unsafe {
264        load_from_file(
265            identifier.as_ptr(),
266            file.as_ptr(),
267            callback.unwrap_or(dummy_receive_texture),
268        )
269    }
270}
271
272/// Loads a texture from the given resource.
273///
274/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
275pub fn load_texture_from_resource(
276    identifier: impl AsRef<str>,
277    resource_id: u32,
278    module: HMODULE,
279    callback: Option<RawTextureReceiveCallback>,
280) {
281    let TextureApi {
282        load_from_resource, ..
283    } = AddonApi::get().texture;
284    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
285    unsafe {
286        load_from_resource(
287            identifier.as_ptr(),
288            resource_id,
289            module,
290            callback.unwrap_or(dummy_receive_texture),
291        )
292    }
293}
294
295/// Loads a texture from the given URL.
296///
297/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
298///
299/// # Usage
300/// ```no_run
301/// # use nexus::texture::*;
302/// # extern "C-unwind" fn receive_texture(_identifier: *const std::ffi::c_char, _texture: *const Texture) {}
303/// load_texture_from_url(
304///     "TEX_DUNGEON_ICON",
305///     "https://render.guildwars2.com",
306///     "/file/943538394A94A491C8632FBEF6203C2013443555/102478.png",
307///     Some(receive_texture),
308/// )
309/// ```
310pub fn load_texture_from_url(
311    identifier: impl AsRef<str>,
312    remote: impl AsRef<str>,
313    endpoint: impl AsRef<str>,
314    callback: Option<RawTextureReceiveCallback>,
315) {
316    let TextureApi { load_from_url, .. } = AddonApi::get().texture;
317    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
318    let remote = str_to_c(remote).expect("failed to convert texture url remote");
319    let endpoint = str_to_c(endpoint).expect("failed to convert texture url endpoint");
320    unsafe {
321        load_from_url(
322            identifier.as_ptr(),
323            remote.as_ptr(),
324            endpoint.as_ptr(),
325            callback.unwrap_or(dummy_receive_texture),
326        )
327    }
328}
329
330/// Loads a texture from the given memory.
331/// ///
332/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
333pub fn load_texture_from_memory(
334    identifier: impl AsRef<str>,
335    data: impl AsRef<[u8]>,
336    callback: Option<RawTextureReceiveCallback>,
337) {
338    let TextureApi {
339        load_from_memory, ..
340    } = AddonApi::get().texture;
341    let identifier = str_to_c(identifier).expect("failed to convert texture identifier");
342    let data = data.as_ref();
343    unsafe {
344        load_from_memory(
345            identifier.as_ptr(),
346            data.as_ptr().cast(),
347            data.len(),
348            callback.unwrap_or(dummy_receive_texture),
349        )
350    }
351}
352
353extern "C-unwind" fn dummy_receive_texture(_identifier: *const c_char, _texture: *const Texture) {}
354
355/// Macro to wrap a texture receive callback.
356///
357/// Generates a [`RawTextureReceiveCallback`] wrapper around the passed callback.
358///
359/// # Usage
360/// ```no_run
361/// # use nexus::texture::*;
362/// use nexus::log::{log, LogLevel};
363/// let texture_receive: RawTextureReceiveCallback = texture_receive!(|id, _texture| {
364///     log(LogLevel::Info, "My Addon", format!("texture {id} loaded"));
365/// });
366/// load_texture_from_file("MY_TEXTURE", r"C:\path\to\texture.png", Some(texture_receive));
367/// ```
368#[macro_export]
369macro_rules! texture_receive {
370    ( $callback:expr $(,)? ) => {{
371        const __CALLBACK: fn(&::std::primitive::str, Option<&$crate::texture::Texture>) = $callback;
372
373        extern "C-unwind" fn __keybind_callback_wrapper(
374            identifier: *const ::std::ffi::c_char,
375            texture: *const $crate::texture::Texture,
376        ) {
377            let identifier = unsafe { $crate::__macro::str_from_c(identifier) }
378                .expect("invalid identifier in texture callback");
379            let texture = unsafe { texture.as_ref() };
380            __CALLBACK(identifier, texture)
381        }
382
383        __keybind_callback_wrapper
384    }};
385}
386
387pub use texture_receive;