mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 05:10:03 -04:00
[example_web] show loading of an image
Required some redesign of `TextureAllocator` as well as some improvements to the fetch API.
This commit is contained in:
@@ -81,12 +81,21 @@ impl WebBackend {
|
||||
}
|
||||
|
||||
impl egui::app::TextureAllocator for webgl::Painter {
|
||||
fn new_texture_srgba_premultiplied(
|
||||
fn alloc(&mut self) -> egui::TextureId {
|
||||
self.alloc_user_texture()
|
||||
}
|
||||
|
||||
fn set_srgba_premultiplied(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
pixels: &[Srgba],
|
||||
) -> egui::TextureId {
|
||||
self.new_user_texture(size, pixels)
|
||||
srgba_pixels: &[Srgba],
|
||||
) {
|
||||
self.set_user_texture(id, size, srgba_pixels);
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,48 +6,80 @@ pub struct Response {
|
||||
pub status: u16,
|
||||
pub status_text: String,
|
||||
|
||||
pub body: String,
|
||||
/// Content-Type header, or empty string if missing
|
||||
pub header_content_type: String,
|
||||
|
||||
/// The raw bytes
|
||||
pub bytes: Vec<u8>,
|
||||
|
||||
/// UTF-8 decoded version of bytes.
|
||||
/// ONLY if `header_content_type` starts with "text" and bytes is UTF-8.
|
||||
pub text: Option<String>,
|
||||
}
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
pub async fn get_text(url: &str) -> Result<Response, String> {
|
||||
get_text_jsvalue(url)
|
||||
pub async fn fetch(method: &str, url: &str) -> Result<Response, String> {
|
||||
fetch_jsvalue(method, url)
|
||||
.await
|
||||
.map_err(|err| err.as_string().unwrap_or_default())
|
||||
}
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
async fn get_text_jsvalue(url: &str) -> Result<Response, JsValue> {
|
||||
pub async fn get(url: &str) -> Result<Response, String> {
|
||||
fetch("GET", url).await
|
||||
}
|
||||
|
||||
/// NOTE: Ok(..) is returned on network error.
|
||||
/// Err is only for failure to use the fetch api.
|
||||
async fn fetch_jsvalue(method: &str, url: &str) -> Result<Response, JsValue> {
|
||||
// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
let mut opts = web_sys::RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.method(method);
|
||||
opts.mode(web_sys::RequestMode::Cors);
|
||||
|
||||
let request = web_sys::Request::new_with_str_and_init(&url, &opts)?;
|
||||
request.headers().set("Accept", "*/*")?;
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
let response = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
assert!(response.is_instance_of::<web_sys::Response>());
|
||||
let response: web_sys::Response = response.dyn_into().unwrap();
|
||||
|
||||
assert!(resp_value.is_instance_of::<web_sys::Response>());
|
||||
let resp: web_sys::Response = resp_value.dyn_into().unwrap();
|
||||
// // TODO: support binary get
|
||||
|
||||
// TODO: headers
|
||||
// TODO: support binary get
|
||||
let body = JsFuture::from(resp.text()?).await?;
|
||||
let body = body.as_string().unwrap_or_default();
|
||||
// let body = JsFuture::from(response.text()?).await?;
|
||||
// let body = body.as_string().unwrap_or_default();
|
||||
|
||||
let array_buffer = JsFuture::from(response.array_buffer()?).await?;
|
||||
let uint8_array = js_sys::Uint8Array::new(&array_buffer);
|
||||
let bytes = uint8_array.to_vec();
|
||||
|
||||
let header_content_type = response
|
||||
.headers()
|
||||
.get("Content-Type")
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let text = if header_content_type.starts_with("text") {
|
||||
String::from_utf8(bytes.clone()).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Response {
|
||||
status_text: resp.status_text(),
|
||||
url: resp.url(),
|
||||
ok: resp.ok(),
|
||||
status: resp.status(),
|
||||
body,
|
||||
status_text: response.status_text(),
|
||||
url: response.url(),
|
||||
ok: response.ok(),
|
||||
status: response.status(),
|
||||
header_content_type,
|
||||
bytes,
|
||||
text,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ pub fn console_log(s: impl Into<JsValue>) {
|
||||
web_sys::console::log_1(&s.into());
|
||||
}
|
||||
|
||||
pub fn console_warn(s: impl Into<JsValue>) {
|
||||
web_sys::console::warn_1(&s.into());
|
||||
}
|
||||
|
||||
pub fn console_error(s: impl Into<JsValue>) {
|
||||
web_sys::console::error_1(&s.into());
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ pub struct Painter {
|
||||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
user_textures: Vec<UserTexture>,
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -104,7 +105,7 @@ struct UserTexture {
|
||||
pixels: Vec<u8>,
|
||||
|
||||
/// Lazily uploaded
|
||||
texture: Option<WebGlTexture>,
|
||||
gl_texture: Option<WebGlTexture>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
@@ -165,28 +166,66 @@ impl Painter {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
pub fn new_user_texture(
|
||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return egui::TextureId::User(i as u64);
|
||||
}
|
||||
}
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_user_texture(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Srgba],
|
||||
) -> egui::TextureId {
|
||||
) {
|
||||
assert_eq!(size.0 * size.1, srgba_pixels.len());
|
||||
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(user_texture) = self.user_textures.get_mut(id as usize) {
|
||||
if let Some(user_texture) = user_texture {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
texture: None,
|
||||
});
|
||||
id
|
||||
*user_texture = UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||
@@ -233,50 +272,40 @@ impl Painter {
|
||||
let gl = &self.gl;
|
||||
|
||||
for user_texture in &mut self.user_textures {
|
||||
if user_texture.texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
if let Some(user_texture) = user_texture {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||
let level = 0;
|
||||
let internal_format = Gl::RGBA;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||
let level = 0;
|
||||
let internal_format = Gl::RGBA;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
user_texture.texture = Some(gl_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> &WebGlTexture {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => &self.egui_texture,
|
||||
egui::TextureId::User(id) => {
|
||||
let id = id as usize;
|
||||
assert!(id < self.user_textures.len());
|
||||
let texture = self.user_textures[id].texture.as_ref();
|
||||
texture.expect("Should have been uploaded")
|
||||
user_texture.gl_texture = Some(gl_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,31 +359,38 @@ impl Painter {
|
||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||
|
||||
for (clip_rect, triangles) in jobs {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(self.get_texture(triangles.texture_id)));
|
||||
if let Some(gl_texture) = self.get_texture(triangles.texture_id) {
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||
|
||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||
let clip_min_x = clamp(clip_min_x, 0.0..=screen_size_pixels.x);
|
||||
let clip_min_y = clamp(clip_min_y, 0.0..=screen_size_pixels.y);
|
||||
let clip_max_x = clamp(clip_max_x, clip_min_x..=screen_size_pixels.x);
|
||||
let clip_max_y = clamp(clip_max_y, clip_min_y..=screen_size_pixels.y);
|
||||
let clip_min_x = clip_min_x.round() as i32;
|
||||
let clip_min_y = clip_min_y.round() as i32;
|
||||
let clip_max_x = clip_max_x.round() as i32;
|
||||
let clip_max_y = clip_max_y.round() as i32;
|
||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||
let clip_min_x = clamp(clip_min_x, 0.0..=screen_size_pixels.x);
|
||||
let clip_min_y = clamp(clip_min_y, 0.0..=screen_size_pixels.y);
|
||||
let clip_max_x = clamp(clip_max_x, clip_min_x..=screen_size_pixels.x);
|
||||
let clip_max_y = clamp(clip_max_y, clip_min_y..=screen_size_pixels.y);
|
||||
let clip_min_x = clip_min_x.round() as i32;
|
||||
let clip_min_y = clip_min_y.round() as i32;
|
||||
let clip_max_x = clip_max_x.round() as i32;
|
||||
let clip_max_y = clip_max_y.round() as i32;
|
||||
|
||||
// scissor Y coordinate is from the bottom
|
||||
gl.scissor(
|
||||
clip_min_x,
|
||||
self.canvas.height() as i32 - clip_max_y,
|
||||
clip_max_x - clip_min_x,
|
||||
clip_max_y - clip_min_y,
|
||||
);
|
||||
// scissor Y coordinate is from the bottom
|
||||
gl.scissor(
|
||||
clip_min_x,
|
||||
self.canvas.height() as i32 - clip_max_y,
|
||||
clip_max_x - clip_min_x,
|
||||
clip_max_y - clip_min_y,
|
||||
);
|
||||
|
||||
for triangles in triangles.split_to_u16() {
|
||||
self.paint_triangles(&triangles)?;
|
||||
for triangles in triangles.split_to_u16() {
|
||||
self.paint_triangles(&triangles)?;
|
||||
}
|
||||
} else {
|
||||
crate::console_warn(format!(
|
||||
"WebGL: Failed to find texture {:?}",
|
||||
triangles.texture_id
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user