Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 26 additions & 32 deletions crates/canvas-2d/src/context/surface_gl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ fn gl_interface() -> Option<Interface> {
}
}

/// 8 bits a channel even when opaque, as on the web: at 5-6 bits (RGB565) a translucent fill never
/// converges on its colour, so a trail fading out leaves a permanent tint. Opaque is RGB888x, whose
/// alpha reads back as 255 whatever was drawn.
fn color_type_for(alpha: bool) -> ColorType {
if alpha {
ColorType::RGBA8888
} else {
ColorType::RGB888x
}
}

fn with_offscreen(
ctx: &mut gpu::DirectContext,
window: skia_safe::Surface,
Expand All @@ -28,23 +39,24 @@ fn with_offscreen(
) -> (skia_safe::Surface, Option<skia_safe::Surface>) {
#[cfg(target_os = "android")]
{
let (color_type, alpha_type) = if alpha {
(ColorType::RGBA8888, AlphaType::Premul)
} else {
(ColorType::RGB565, AlphaType::Opaque)
};
let color_type = color_type_for(alpha);
let alpha_type = if alpha { AlphaType::Premul } else { AlphaType::Opaque };
let info = ImageInfo::new(
ISize::new(window.width(), window.height()),
color_type,
alpha_type,
color_space,
);
let props = window.props().clone();
// Multisampled, so Skia draws antialiased paths on the GPU instead of rasterizing their
// coverage on this thread and uploading it every frame; on a tiled GPU the samples mostly
// stay in tile memory.
let samples = ctx.max_surface_sample_count_for_color_type(color_type).min(4);
if let Some(offscreen) = gpu::surfaces::render_target(
ctx,
gpu::Budgeted::Yes,
&info,
Some(0),
Some(samples),
gpu::SurfaceOrigin::TopLeft,
Some(&props),
false,
Expand All @@ -69,8 +81,9 @@ fn draws_to_window(gl: &canvas_core::gpu::gl::GLContext) -> bool {
}
}

const GR_GL_RGB565: u32 = 0x8D62;
const GR_GL_RGBA8: u32 = 0x8058;
/// An opaque config's buffer, which RGB888x wraps.
const GR_GL_RGB8: u32 = 0x8051;

#[cfg(feature = "gl")]
impl Context {
Expand Down Expand Up @@ -150,11 +163,7 @@ impl Context {

let mut frame_buffer = gpu::gl::FramebufferInfo::from_fboid(buffer_id[0] as u32);

if alpha {
frame_buffer.format = GR_GL_RGBA8;
} else {
frame_buffer.format = GR_GL_RGB565;
}
frame_buffer.format = if alpha { GR_GL_RGBA8 } else { GR_GL_RGB8 };

let target = gpu::backend_render_targets::make_gl(
(width as i32, height as i32),
Expand All @@ -166,10 +175,7 @@ impl Context {
skia_safe::SurfacePropsFlags::default(),
PixelGeometry::Unknown,
);
let mut color_type = ColorType::RGBA8888;
if !alpha {
color_type = ColorType::RGB565;
}
let color_type = color_type_for(alpha);

let surface = gpu::surfaces::wrap_backend_render_target(
&mut ctx,
Expand Down Expand Up @@ -310,16 +316,12 @@ impl Context {
let mut window_surface = None;
let mut engine = SurfaceEngine::GL;
let surface = if bounds.is_empty() {
let color_type = if alpha {
ColorType::RGBA8888
} else {
ColorType::RGB565
};
let color_type = color_type_for(alpha);

let alpha_type = if alpha {
AlphaType::Unpremul
} else {
AlphaType::Premul
AlphaType::Opaque
};

let mut width = width;
Expand Down Expand Up @@ -353,11 +355,7 @@ impl Context {

let mut frame_buffer = gpu::gl::FramebufferInfo::from_fboid(buffer_id as u32);

if alpha {
frame_buffer.format = GR_GL_RGBA8;
} else {
frame_buffer.format = GR_GL_RGB565;
}
frame_buffer.format = if alpha { GR_GL_RGBA8 } else { GR_GL_RGB8 };

let target = gpu::backend_render_targets::make_gl(
(width as i32, height as i32),
Expand All @@ -370,11 +368,7 @@ impl Context {
skia_safe::SurfacePropsFlags::default(),
PixelGeometry::Unknown,
);
let mut color_type = ColorType::RGBA8888;

if !alpha {
color_type = ColorType::RGB565;
}
let color_type = color_type_for(alpha);

let surface = gpu::surfaces::wrap_backend_render_target(
&mut ctx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ pub extern "system" fn nativeInitWebGL(
desynchronized: jboolean,
xr_compatible: jboolean,
version: jint,
threaded: jboolean,
) -> jlong {
unsafe {
let interface = env.get_native_interface();
Expand All @@ -261,7 +262,12 @@ pub extern "system" fn nativeInitWebGL(
return 0;
}
if let Ok(power_preference) = PowerPreference::try_from(power_preference) {
let context = canvas_c::canvas_native_webgl_create(
let create = if threaded == JNI_TRUE {
canvas_c::canvas_native_webgl_create_threaded
} else {
canvas_c::canvas_native_webgl_create
};
let context = create(
window.ptr().as_ptr() as _,
window.width(),
window.height(),
Expand Down Expand Up @@ -308,6 +314,7 @@ pub extern "system" fn nativeInitWebGLNoSurface(
desynchronized: jboolean,
xr_compatible: jboolean,
version: jint,
threaded: jboolean,
) -> jlong {
if version == 2 && !GLContext::has_gl2support() {
return 0;
Expand All @@ -317,7 +324,12 @@ pub extern "system" fn nativeInitWebGLNoSurface(
let height = height.max(1);

if let Ok(power_preference) = PowerPreference::try_from(power_preference) {
let context = canvas_c::canvas_native_webgl_create_no_window(
let create = if threaded == JNI_TRUE {
canvas_c::canvas_native_webgl_create_no_window_threaded
} else {
canvas_c::canvas_native_webgl_create_no_window
};
let context = create(
width,
height,
version as i32,
Expand Down Expand Up @@ -458,19 +470,23 @@ pub extern "system" fn nativeUpdateWebGLSurface(
return;
}
let context = context as *mut WebGLState;
let context = unsafe { &mut *context };
let context = unsafe { &*context };
unsafe {
if let Some(window) = NativeWindow::from_surface(env.get_native_interface(), surface) {
// NativeWindow::ptr() is NonNull<ANativeWindow>; the as *mut c_void cast
// preserves non-nullness, but guard defensively to avoid a panic.
let Some(nn_ptr) = NonNull::new(window.ptr().as_ptr() as _) else { return };
context.get_inner_mut().set_window_surface(
window.width(),
window.height(),
nn_ptr,
);
context.get_inner().make_current();
drop(env);
let (width, height) = (window.width(), window.height());
// Queued, not waited on: this runs from the view's surface callbacks, and blocking them
// on a thread that may be waiting for a buffer from this window can deadlock its queue.
// `window` holds a reference until the job is done with it.
context.post(move |state| {
// NativeWindow::ptr() is NonNull<ANativeWindow>; the as *mut c_void cast
// preserves non-nullness, but guard defensively to avoid a panic.
let Some(nn_ptr) = NonNull::new(window.ptr().as_ptr() as _) else {
return;
};
state.set_window_surface(width, height, nn_ptr);
state.make_current();
});
}
}
}
Expand Down Expand Up @@ -616,9 +632,12 @@ fn native_update_gl_no_surface(width: jint, height: jint, context: jlong) {
return;
}
let context = context as *mut WebGLState;
let context = unsafe { &mut *context };
context.get_inner().make_current();
context.get_inner_mut().resize_pbuffer(width, height);
let context = unsafe { &*context };
// Waited on: when the window is going away this is how the context lets go of it.
context.detach(|state| {
state.make_current();
state.resize_pbuffer(width, height);
});
}

#[no_mangle]
Expand Down Expand Up @@ -676,7 +695,8 @@ pub extern "system" fn nativeMakeWebGLCurrent(gl_context: jlong) -> jboolean {
}
let gl_context = gl_context as *mut WebGLState;
let gl_context = unsafe { &*gl_context };
if gl_context.get_inner().make_current() {
// A threaded context is made current on its own thread, where everything that uses it runs.
if gl_context.sync(|state| state.make_current()) {
return JNI_TRUE;
}
JNI_FALSE
Expand All @@ -693,7 +713,8 @@ pub extern "system" fn nativeMakeWebGLCurrentNormal(
}
let gl_context = gl_context as *mut WebGLState;
let gl_context = unsafe { &*gl_context };
if gl_context.get_inner().make_current() {
// A threaded context is made current on its own thread, where everything that uses it runs.
if gl_context.sync(|state| state.make_current()) {
return JNI_TRUE;
}
JNI_FALSE
Expand Down Expand Up @@ -851,32 +872,35 @@ pub extern "system" fn nativeWriteCurrentWebGLContextToBitmap(
}

let context = context as *mut WebGLState;
let context = unsafe { &mut *context };
let context = unsafe { &*context };

unsafe {
crate::utils::image::bitmap_handler(
&env,
bitmap,
Box::new(move |cb| {
if let Some((image_data, info)) = cb {
context.get_inner().make_current();
// Use checked arithmetic — width/height are u32 and can overflow on multiply.
let buf_size = (info.width() as usize)
.checked_mul(info.height() as usize)
.and_then(|n| n.checked_mul(4));
let Some(buf_size) = buf_size else { return };
let mut buf = vec![0u8; buf_size];
gl_bindings::Flush();
gl_bindings::ReadPixels(
0,
0,
info.width() as i32,
info.height() as i32,
gl_bindings::RGBA as std::os::raw::c_uint,
gl_bindings::UNSIGNED_BYTE as std::os::raw::c_uint,
buf.as_mut_ptr() as *mut c_void,
);
image_data.copy_from_slice(buf.as_slice());
// The bitmap stays locked while this waits for the read.
context.sync(|state| {
state.make_current();
// Use checked arithmetic — width/height are u32 and can overflow on multiply.
let buf_size = (info.width() as usize)
.checked_mul(info.height() as usize)
.and_then(|n| n.checked_mul(4));
let Some(buf_size) = buf_size else { return };
let mut buf = vec![0u8; buf_size];
gl_bindings::Flush();
gl_bindings::ReadPixels(
0,
0,
info.width() as i32,
info.height() as i32,
gl_bindings::RGBA as std::os::raw::c_uint,
gl_bindings::UNSIGNED_BYTE as std::os::raw::c_uint,
buf.as_mut_ptr() as *mut c_void,
);
image_data.copy_from_slice(buf.as_slice());
});
}
}),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ pub extern "system" fn nativeTexImage2D(
}

let context = context as *mut WebGLState;
let context = unsafe { &mut *context };
let context = unsafe { &*context };

let bytes = crate::utils::image::get_bytes_from_bitmap(&env, bitmap);

if let Some((bytes, info)) = bytes {
context.get_inner().make_current();
let width = info.width();
let height = info.height();
unsafe {
let stride = info.stride();
// The bitmap's bytes are a copy, so the upload can queue.
context.post(move |state| unsafe {
state.make_current();
if flip_y == JNI_TRUE {
let mut buffer = bytes;
canvas_webgl::utils::gl::flip_in_place(
buffer.as_mut_ptr(),
buffer.len(),
info.stride() as usize,
stride as usize,
height as usize,
);

Expand Down Expand Up @@ -65,7 +67,7 @@ pub extern "system" fn nativeTexImage2D(
bytes.as_ptr() as *const c_void,
);
}
}
});
}
}

Expand All @@ -88,21 +90,23 @@ pub extern "system" fn nativeTexSubImage2D(
}

let context = context as *mut WebGLState;
let context = unsafe { &mut *context };
let context = unsafe { &*context };

let bytes = crate::utils::image::get_bytes_from_bitmap(&env, bitmap);

if let Some((bytes, info)) = bytes {
unsafe {
context.get_inner().make_current();
let width = info.width();
let height = info.height();
let width = info.width();
let height = info.height();
let stride = info.stride();
// The bitmap's bytes are a copy, so the upload can queue.
context.post(move |state| unsafe {
state.make_current();
if flip_y == JNI_TRUE {
let mut buffer = bytes;
canvas_webgl::utils::gl::flip_in_place(
buffer.as_mut_ptr(),
buffer.len(),
info.stride() as usize,
stride as usize,
height as usize,
);

Expand Down Expand Up @@ -130,6 +134,6 @@ pub extern "system" fn nativeTexSubImage2D(
bytes.as_ptr() as *const c_void,
);
}
}
});
}
}
8 changes: 4 additions & 4 deletions crates/canvas-android/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ pub extern "system" fn JNI_OnLoad(vm: JavaVM, _reserved: *const c_void) -> jint

let canvas_signatures = if ret >= ANDROID_O {
let mut ret = vec![
"(Landroid/view/Surface;ZZZZIZZZZZI)J",
"(IIZZZZIZZZZZI)J",
"(Landroid/view/Surface;ZZZZIZZZZZIZ)J",
"(IIZZZZIZZZZZIZ)J",
"(IILandroid/view/Surface;ZFIFII)J",
"(Landroid/view/Surface;J)V",
"(Landroid/view/Surface;IIJ)V",
Expand Down Expand Up @@ -149,8 +149,8 @@ pub extern "system" fn JNI_OnLoad(vm: JavaVM, _reserved: *const c_void) -> jint
ret
} else {
let mut ret = vec![
"!(Landroid/view/Surface;ZZZZIZZZZZI)J",
"!(IIZZZZIZZZZZI)J",
"(Landroid/view/Surface;ZZZZIZZZZZIZ)J",
"(IIZZZZIZZZZZIZ)J",
"!(IILandroid/view/Surface;ZFIFII)J",
"!(Landroid/view/Surface;J)V",
"!(Landroid/view/Surface;IIJ)V",
Expand Down
Loading
Loading