nexus\api/
texture.rs

1//! Texture loading.
2
3use crate::{
4    util::{path_to_c, str_to_c},
5    AddonApi, TextureApi,
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, "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.
123pub fn get_texture_or_create_from_file(
124    identifier: impl AsRef<str>,
125    file: impl AsRef<Path>,
126) -> Option<Texture> {
127    let TextureApi {
128        get_or_create_from_file,
129        ..
130    } = AddonApi::get().texture;
131    let identifier = str_to_c(identifier, "failed to convert texture identifier");
132    let file = path_to_c(file, "failed to convert texture file");
133    unsafe {
134        get_or_create_from_file(identifier.as_ptr(), file.as_ptr())
135            .as_ref()
136            .cloned()
137    }
138}
139
140/// Attempts to retrieve a texture or creates it from the given resource.
141pub fn get_texture_or_create_from_resource(
142    identifier: impl AsRef<str>,
143    resource_id: u32,
144    module: HMODULE,
145) -> Option<Texture> {
146    let TextureApi {
147        get_or_create_from_resource,
148        ..
149    } = AddonApi::get().texture;
150    let identifier = str_to_c(identifier, "failed to convert texture identifier");
151    unsafe {
152        get_or_create_from_resource(identifier.as_ptr(), resource_id, module)
153            .as_ref()
154            .cloned()
155    }
156}
157
158/// Attempts to retrieve a texture or creates it from the given URL.
159pub fn get_texture_or_create_from_url(
160    identifier: impl AsRef<str>,
161    remote: impl AsRef<str>,
162    endpoint: impl AsRef<str>,
163) -> Option<Texture> {
164    let TextureApi {
165        get_or_create_from_url,
166        ..
167    } = AddonApi::get().texture;
168    let identifier = str_to_c(identifier, "failed to convert texture identifier");
169    let remote = str_to_c(remote, "failed to convert texture url remote");
170    let endpoint = str_to_c(endpoint, "failed to convert texture url endpoint");
171    unsafe {
172        get_or_create_from_url(identifier.as_ptr(), remote.as_ptr(), endpoint.as_ptr())
173            .as_ref()
174            .cloned()
175    }
176}
177
178/// Attempts to retrieve a texture or creates it from the given memory.
179pub fn get_texture_or_create_from_memory(
180    identifier: impl AsRef<str>,
181    memory: impl AsRef<[u8]>,
182) -> Option<Texture> {
183    let TextureApi {
184        get_or_create_from_memory,
185        ..
186    } = AddonApi::get().texture;
187    let identifier = str_to_c(identifier, "failed to convert texture identifier");
188    let memory = memory.as_ref();
189    unsafe {
190        get_or_create_from_memory(identifier.as_ptr(), memory.as_ptr().cast(), memory.len())
191            .as_ref()
192            .cloned()
193    }
194}
195
196/// Loads a texture from the given file path.
197///
198/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
199pub fn load_texture_from_file(
200    identifier: impl AsRef<str>,
201    file: impl AsRef<Path>,
202    callback: Option<RawTextureReceiveCallback>,
203) {
204    let TextureApi { load_from_file, .. } = AddonApi::get().texture;
205    let identifier = str_to_c(identifier, "failed to convert texture identifier");
206    let file = path_to_c(file, "foo");
207    unsafe {
208        load_from_file(
209            identifier.as_ptr(),
210            file.as_ptr(),
211            callback.unwrap_or(dummy_receive_texture),
212        )
213    }
214}
215
216/// Loads a texture from the given resource.
217///
218/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
219pub fn load_texture_from_resource(
220    identifier: impl AsRef<str>,
221    resource_id: u32,
222    module: HMODULE,
223    callback: Option<RawTextureReceiveCallback>,
224) {
225    let TextureApi {
226        load_from_resource, ..
227    } = AddonApi::get().texture;
228    let identifier = str_to_c(identifier, "failed to convert texture identifier");
229    unsafe {
230        load_from_resource(
231            identifier.as_ptr(),
232            resource_id,
233            module,
234            callback.unwrap_or(dummy_receive_texture),
235        )
236    }
237}
238
239/// Loads a texture from the given URL.
240///
241/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
242///
243/// # Usage
244/// ```no_run
245/// # use nexus::texture::*;
246/// # extern "C-unwind" fn receive_texture(_identifier: *const std::ffi::c_char, _texture: *const Texture) {}
247/// load_texture_from_url(
248///     "TEX_DUNGEON_ICON",
249///     "https://render.guildwars2.com",
250///     "/file/943538394A94A491C8632FBEF6203C2013443555/102478.png",
251///     Some(receive_texture),
252/// )
253/// ```
254pub fn load_texture_from_url(
255    identifier: impl AsRef<str>,
256    remote: impl AsRef<str>,
257    endpoint: impl AsRef<str>,
258    callback: Option<RawTextureReceiveCallback>,
259) {
260    let TextureApi { load_from_url, .. } = AddonApi::get().texture;
261    let identifier = str_to_c(identifier, "failed to convert texture identifier");
262    let remote = str_to_c(remote, "failed to convert texture url remote");
263    let endpoint = str_to_c(endpoint, "failed to convert texture url endpoint");
264    unsafe {
265        load_from_url(
266            identifier.as_ptr(),
267            remote.as_ptr(),
268            endpoint.as_ptr(),
269            callback.unwrap_or(dummy_receive_texture),
270        )
271    }
272}
273
274/// Loads a texture from the given memory.
275/// ///
276/// You can create a [`RawTextureReceiveCallback`] using the [`texture_receive`] macro.
277pub fn load_texture_from_memory(
278    identifier: impl AsRef<str>,
279    data: impl AsRef<[u8]>,
280    callback: Option<RawTextureReceiveCallback>,
281) {
282    let TextureApi {
283        load_from_memory, ..
284    } = AddonApi::get().texture;
285    let identifier = str_to_c(identifier, "failed to convert texture identifier");
286    let data = data.as_ref();
287    unsafe {
288        load_from_memory(
289            identifier.as_ptr(),
290            data.as_ptr().cast(),
291            data.len(),
292            callback.unwrap_or(dummy_receive_texture),
293        )
294    }
295}
296
297extern "C-unwind" fn dummy_receive_texture(_identifier: *const c_char, _texture: *const Texture) {}
298
299/// Macro to wrap a texture receive callback.
300///
301/// Generates a [`RawTextureReceiveCallback`] wrapper around the passed callback.
302///
303/// # Usage
304/// ```no_run
305/// # use nexus::texture::*;
306/// use nexus::log::{log, LogLevel};
307/// let texture_receive: RawTextureReceiveCallback = texture_receive!(|id, _texture| {
308///     log(LogLevel::Info, "My Addon", format!("texture {id} loaded"));
309/// });
310/// load_texture_from_file("MY_TEXTURE", r"C:\path\to\texture.png", Some(texture_receive));
311/// ```
312#[macro_export]
313macro_rules! texture_receive {
314    ( $callback:expr $(,)? ) => {{
315        const __CALLBACK: fn(&::std::primitive::str, Option<&$crate::texture::Texture>) = $callback;
316
317        extern "C-unwind" fn __keybind_callback_wrapper(
318            identifier: *const ::std::ffi::c_char,
319            texture: *const $crate::texture::Texture,
320        ) {
321            let identifier = unsafe { $crate::__macro::str_from_c(identifier) }
322                .expect("invalid identifier in texture callback");
323            let texture = unsafe { texture.as_ref() };
324            __CALLBACK(identifier, texture)
325        }
326
327        __keybind_callback_wrapper
328    }};
329}
330
331pub use texture_receive;