1
0
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:
Emil Ernerfeldt
2020-11-18 21:38:29 +01:00
parent 90cecace0c
commit c6ce0b9e8c
16 changed files with 645 additions and 277 deletions

View File

@@ -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)
}
}

View File

@@ -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,
})
}

View File

@@ -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());
}

View File

@@ -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(())