use std::ffi::{c_void, CStr};
use super::super::cache::{BufferImportKey, CacheKind};
use super::super::core::float_crop_uniforms;
use super::super::platform::GlPlatform;
use super::{
check_gl_error, dyn_to_u8_src, plan_pbo_readback, spread_rows, with_mapped_pbo, GLProcessorST,
PboReadbackLayout,
};
use crate::{Error, Flip, ResolvedCrop, Rotation};
use edgefirst_tensor::{DType, PixelFormat, Tensor, TensorDyn, TensorMemory, TensorTrait};
pub(in super::super) use super::super::float_dispatch::{classify_float_render, FloatRenderPath};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in super::super) enum FloatSrcFeed {
Import,
Pbo,
Upload,
}
fn float_src_import_disabled() -> bool {
static DISABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*DISABLED.get_or_init(|| {
std::env::var("EDGEFIRST_GL_NO_FLOAT_SRC_IMPORT")
.map(|v| v == "1")
.unwrap_or(false)
})
}
pub(in super::super) fn float_render_support(
is_vivante: bool,
float_import_refused: bool,
f32_ext: bool,
f16_ext: bool,
) -> crate::RenderDtypeSupport {
if is_vivante || float_import_refused {
return crate::RenderDtypeSupport {
f32: false,
f16: false,
};
}
crate::RenderDtypeSupport {
f32: f32_ext,
f16: f16_ext,
}
}
pub(in super::super) fn packed_planar_layout(w: u32, h: u32, channels: u32) -> Option<(u32, u32)> {
if !w.is_multiple_of(4) {
return None;
}
Some((w / 4, channels.checked_mul(h)?))
}
fn packed_interleaved_layout(w: u32, h: u32) -> Option<(u32, u32)> {
let row = w.checked_mul(3)?;
if !row.is_multiple_of(4) {
return None;
}
Some((row / 4, h))
}
fn float_pbo_buffer_id<T>(t: &edgefirst_tensor::Tensor<T>) -> crate::Result<u32>
where
T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync + edgefirst_tensor::Element,
{
let buffer_id = t.pbo_id().ok_or_else(|| {
crate::Error::OpenGl("convert_float_to_pbo: dst is not a PBO tensor".to_string())
})?;
if t.pbo_is_mapped() == Some(true) {
return Err(crate::Error::OpenGl(
"Cannot convert to a mapped PBO tensor".to_string(),
));
}
Ok(buffer_id)
}
pub(super) unsafe fn finish_via_fence() {
unsafe {
const TIMEOUT_NS: u64 = 1_000_000_000;
let sync = edgefirst_gl::gl::FenceSync(edgefirst_gl::gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
if sync.is_null() {
edgefirst_gl::gl::Finish();
return;
}
let status = edgefirst_gl::gl::ClientWaitSync(
sync,
edgefirst_gl::gl::SYNC_FLUSH_COMMANDS_BIT,
TIMEOUT_NS,
);
edgefirst_gl::gl::DeleteSync(sync);
match status {
s if s == edgefirst_gl::gl::ALREADY_SIGNALED
|| s == edgefirst_gl::gl::CONDITION_SATISFIED => {}
_ => edgefirst_gl::gl::Finish(),
}
}
}
unsafe fn read_float_pixels_into_pbo(
layout: &PboReadbackLayout,
buffer_id: u32,
packed_w: usize,
rows: usize,
client_fmt: u32,
gl_type: u32,
) -> crate::Result<()> {
let &PboReadbackLayout {
stride,
tight_row,
needed,
row_length,
} = layout;
unsafe {
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, buffer_id);
edgefirst_gl::gl::ReadBuffer(edgefirst_gl::gl::COLOR_ATTACHMENT0);
if let Some(pixels) = row_length {
edgefirst_gl::gl::PixelStorei(edgefirst_gl::gl::PACK_ROW_LENGTH, pixels);
}
edgefirst_gl::gl::ReadPixels(
0,
0,
packed_w as i32,
rows as i32,
client_fmt,
gl_type,
std::ptr::null_mut(),
);
if row_length.is_some() {
edgefirst_gl::gl::PixelStorei(edgefirst_gl::gl::PACK_ROW_LENGTH, 0);
}
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
finish_via_fence();
}
check_gl_error(function!(), line!())?;
if stride > tight_row && row_length.is_none() {
unsafe {
with_mapped_pbo(
buffer_id,
needed,
edgefirst_gl::gl::MAP_READ_BIT | edgefirst_gl::gl::MAP_WRITE_BIT,
"float PBO readback row placement",
|buf| spread_rows(buf, rows, tight_row, stride),
)?;
}
check_gl_error(function!(), line!())?;
}
Ok(())
}
impl GLProcessorST {
pub(super) fn feed_float_src(
&mut self,
src_u8: &Tensor<u8>,
src_w: usize,
src_h: usize,
src_filter: i32,
convert_span: &tracing::Span,
) -> crate::Result<FloatSrcFeed> {
let src_tex_id = self.camera_normal_texture.id;
unsafe {
edgefirst_gl::gl::ActiveTexture(edgefirst_gl::gl::TEXTURE0);
edgefirst_gl::gl::BindTexture(edgefirst_gl::gl::TEXTURE_2D, src_tex_id);
edgefirst_gl::gl::TexParameteri(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::TEXTURE_MIN_FILTER,
src_filter,
);
edgefirst_gl::gl::TexParameteri(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::TEXTURE_MAG_FILTER,
src_filter,
);
edgefirst_gl::gl::TexParameteri(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::TEXTURE_WRAP_S,
edgefirst_gl::gl::CLAMP_TO_EDGE as i32,
);
edgefirst_gl::gl::TexParameteri(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::TEXTURE_WRAP_T,
edgefirst_gl::gl::CLAMP_TO_EDGE as i32,
);
for (swizzle, comp) in [
(edgefirst_gl::gl::TEXTURE_SWIZZLE_R, edgefirst_gl::gl::RED),
(edgefirst_gl::gl::TEXTURE_SWIZZLE_G, edgefirst_gl::gl::GREEN),
(edgefirst_gl::gl::TEXTURE_SWIZZLE_B, edgefirst_gl::gl::BLUE),
(edgefirst_gl::gl::TEXTURE_SWIZZLE_A, edgefirst_gl::gl::ALPHA),
] {
edgefirst_gl::gl::TexParameteri(edgefirst_gl::gl::TEXTURE_2D, swizzle, comp as i32);
}
}
let _ = src_tex_id;
if !float_src_import_disabled()
&& self.gl_context.transfer_backend.is_zero_copy()
&& src_u8.memory() == TensorMemory::DmaBuf
{
let key = BufferImportKey::from_tensor(src_u8, PixelFormat::Rgba, false);
match self.get_or_create_egl_image(CacheKind::Src, src_u8, PixelFormat::Rgba) {
Ok(handle) => {
let attach = unsafe {
self.camera_normal_texture
.bind_egl_image(&self.gl_context, key, handle)
};
match attach.and_then(|_| check_gl_error(function!(), line!())) {
Ok(()) => {
self.camera_normal_texture.forget_storage();
self.convert_stats.src_imports += 1;
convert_span.record("src_feed", "import");
return Ok(FloatSrcFeed::Import);
}
Err(e) => {
self.camera_normal_texture.invalidate_egl_binding();
self.convert_stats.zero_copy_declines += 1;
log::debug!(
"float src zero-copy attach failed ({e:?}); uploading instead"
);
}
}
}
Err(e) => {
self.convert_stats.zero_copy_declines += 1;
log::debug!("float src zero-copy import failed ({e:?}); uploading instead");
}
}
}
const SRC_BPP: usize = 4;
let row_len_px =
super::unpack_row_length(src_u8.effective_row_stride(), src_w, SRC_BPP, "RGBA")?;
let pixels = super::SrcPixels::open(src_u8, &self.owned_pbos)?;
pixels.record(&mut self.convert_stats, convert_span);
let row_stride_b = if row_len_px == 0 {
src_w * SRC_BPP
} else {
row_len_px as usize * SRC_BPP
};
let required = if src_h == 0 {
0
} else {
(src_h - 1) * row_stride_b + src_w * SRC_BPP
};
let uploaded = unsafe {
edgefirst_gl::gl::PixelStorei(edgefirst_gl::gl::UNPACK_ROW_LENGTH, row_len_px);
let uploaded = self.camera_normal_texture.upload(
edgefirst_gl::gl::TEXTURE_2D,
src_w,
src_h,
edgefirst_gl::gl::RGBA,
required,
pixels.source(),
);
edgefirst_gl::gl::PixelStorei(edgefirst_gl::gl::UNPACK_ROW_LENGTH, 0);
uploaded
};
uploaded?;
Ok(match pixels {
super::SrcPixels::Pbo { .. } => FloatSrcFeed::Pbo,
super::SrcPixels::Mapped(_) => FloatSrcFeed::Upload,
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn draw_float_quad(
&mut self,
program_id: u32,
sampler_name: &CStr,
src_tex_id: u32,
packed_w: u32,
packed_h: u32,
src_rect_uv: [f32; 4],
src_extent: [f32; 4],
dst_rect_px: [f32; 4],
pad_color: [f32; 4],
dst_image_size: Option<(f32, f32)>,
) -> crate::Result<()> {
let locs = *self
.float_quad_locs
.entry(program_id)
.or_insert_with(|| unsafe {
let loc =
|name: &CStr| edgefirst_gl::gl::GetUniformLocation(program_id, name.as_ptr());
super::FloatQuadLocs {
sampler: loc(sampler_name),
src_rect_uv: loc(c"src_rect_uv"),
src_extent: loc(c"src_extent"),
dst_rect_px: loc(c"dst_rect_px"),
pad_color: loc(c"pad_color"),
dst_image_size: loc(c"dst_image_size"),
norm_scale: loc(c"norm_scale"),
norm_offset: loc(c"norm_offset"),
}
});
let (pos_vbo, uv_vbo) = self.ensure_float_quad_vbos();
unsafe {
edgefirst_gl::gl::Viewport(0, 0, packed_w as i32, packed_h as i32);
edgefirst_gl::gl::UseProgram(program_id);
edgefirst_gl::gl::ActiveTexture(edgefirst_gl::gl::TEXTURE0);
edgefirst_gl::gl::BindTexture(edgefirst_gl::gl::TEXTURE_2D, src_tex_id);
edgefirst_gl::gl::Uniform1i(locs.sampler, 0);
edgefirst_gl::gl::Uniform4f(
locs.src_rect_uv,
src_rect_uv[0],
src_rect_uv[1],
src_rect_uv[2],
src_rect_uv[3],
);
edgefirst_gl::gl::Uniform4f(
locs.src_extent,
src_extent[0],
src_extent[1],
src_extent[2],
src_extent[3],
);
edgefirst_gl::gl::Uniform4f(
locs.dst_rect_px,
dst_rect_px[0],
dst_rect_px[1],
dst_rect_px[2],
dst_rect_px[3],
);
let norm = self.float_norm;
let pad_color = norm.pad.unwrap_or(pad_color);
edgefirst_gl::gl::Uniform4f(
locs.pad_color,
pad_color[0],
pad_color[1],
pad_color[2],
pad_color[3],
);
edgefirst_gl::gl::Uniform4fv(locs.norm_scale, 1, norm.scale.as_ptr());
edgefirst_gl::gl::Uniform4fv(locs.norm_offset, 1, norm.offset.as_ptr());
if let Some((w, h)) = dst_image_size {
edgefirst_gl::gl::Uniform2f(locs.dst_image_size, w, h);
}
check_gl_error(function!(), line!())?;
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, pos_vbo);
edgefirst_gl::gl::VertexAttribPointer(
self.vertex_buffer.buffer_index,
3,
edgefirst_gl::gl::FLOAT,
edgefirst_gl::gl::FALSE,
0,
std::ptr::null(),
);
edgefirst_gl::gl::EnableVertexAttribArray(self.vertex_buffer.buffer_index);
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, uv_vbo);
edgefirst_gl::gl::VertexAttribPointer(
self.texture_buffer.buffer_index,
2,
edgefirst_gl::gl::FLOAT,
edgefirst_gl::gl::FALSE,
0,
std::ptr::null(),
);
edgefirst_gl::gl::EnableVertexAttribArray(self.texture_buffer.buffer_index);
let quad_index: [u32; 4] = [0, 1, 2, 3];
edgefirst_gl::gl::DrawElements(
edgefirst_gl::gl::TRIANGLE_FAN,
quad_index.len() as i32,
edgefirst_gl::gl::UNSIGNED_INT,
quad_index.as_ptr() as *const c_void,
);
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, self.vertex_buffer.id);
edgefirst_gl::gl::VertexAttribPointer(
self.vertex_buffer.buffer_index,
3,
edgefirst_gl::gl::FLOAT,
edgefirst_gl::gl::FALSE,
0,
std::ptr::null(),
);
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, self.texture_buffer.id);
edgefirst_gl::gl::VertexAttribPointer(
self.texture_buffer.buffer_index,
2,
edgefirst_gl::gl::FLOAT,
edgefirst_gl::gl::FALSE,
0,
std::ptr::null(),
);
check_gl_error(function!(), line!())?;
}
Ok(())
}
fn ensure_float_quad_vbos(&mut self) -> (u32, u32) {
if self.float_quad_pos_vbo == 0 {
const QUAD_POS: [f32; 12] = [
-1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0, ];
const QUAD_UV: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
unsafe {
let mut ids = [0u32; 2];
edgefirst_gl::gl::GenBuffers(2, ids.as_mut_ptr());
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, ids[0]);
edgefirst_gl::gl::BufferData(
edgefirst_gl::gl::ARRAY_BUFFER,
std::mem::size_of_val(&QUAD_POS) as isize,
QUAD_POS.as_ptr() as *const c_void,
edgefirst_gl::gl::STATIC_DRAW,
);
edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::ARRAY_BUFFER, ids[1]);
edgefirst_gl::gl::BufferData(
edgefirst_gl::gl::ARRAY_BUFFER,
std::mem::size_of_val(&QUAD_UV) as isize,
QUAD_UV.as_ptr() as *const c_void,
edgefirst_gl::gl::STATIC_DRAW,
);
self.float_quad_pos_vbo = ids[0];
self.float_quad_uv_vbo = ids[1];
}
}
(self.float_quad_pos_vbo, self.float_quad_uv_vbo)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn convert_float_to_pbo(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
path: FloatRenderPath,
rotation: crate::Rotation,
flip: Flip,
crop: ResolvedCrop,
convert_span: &tracing::Span,
) -> crate::Result<()> {
let (internal, client_fmt, gl_type, pixel_bytes) = match path {
FloatRenderPath::PboF32Nhwc => (
edgefirst_gl::gl::R32F,
edgefirst_gl::gl::RED,
edgefirst_gl::gl::FLOAT,
4,
),
FloatRenderPath::PboF16Nchw | FloatRenderPath::PboF16Nhwc => (
edgefirst_gl::gl::RGBA16F,
edgefirst_gl::gl::RGBA,
edgefirst_gl::gl::HALF_FLOAT,
8,
),
FloatRenderPath::PboF32Nchw => (
edgefirst_gl::gl::RGBA32F,
edgefirst_gl::gl::RGBA,
edgefirst_gl::gl::FLOAT,
16,
),
FloatRenderPath::ZeroCopyF16Nchw
| FloatRenderPath::ZeroCopyF32Nchw
| FloatRenderPath::ZeroCopyFloatNhwc
| FloatRenderPath::ZeroCopyFloatRgba
| FloatRenderPath::None => {
return Err(crate::Error::NotSupported(
"GL float render-to-PBO: not a PBO path; using CPU fallback".to_string(),
));
}
};
if rotation != Rotation::None || flip != Flip::None {
return Err(crate::Error::NotSupported(
"GL float render-to-PBO: rotation/flip not supported on the float path; \
using CPU fallback"
.to_string(),
));
}
let (src_u8, src_fmt) = dyn_to_u8_src(src)?;
if src_fmt != PixelFormat::Rgba {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-PBO: source format must be Rgba, got {src_fmt:?}; \
using CPU fallback"
)));
}
let src_w = src_u8.width().ok_or(Error::NotAnImage)?;
let src_h = src_u8.height().ok_or(Error::NotAnImage)?;
let dst_w = dst.width().ok_or(Error::NotAnImage)?;
let dst_h = dst.height().ok_or(Error::NotAnImage)?;
let dst_buffer_id = match dst.dtype() {
DType::F32 => float_pbo_buffer_id(dst.as_typed::<f32>().expect("dtype checked"))?,
DType::F16 => float_pbo_buffer_id(dst.as_typed::<half::f16>().expect("dtype checked"))?,
other => {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-PBO: dst dtype must be F32 or F16, got {other:?}"
)));
}
};
let (packed_w, packed_h) = match path {
FloatRenderPath::PboF32Nhwc => (dst_w * 3, dst_h),
FloatRenderPath::PboF16Nchw => {
let layout = edgefirst_tensor::packed_rgba16f_layout(
PixelFormat::PlanarRgb,
edgefirst_tensor::DType::F16,
dst_w,
dst_h,
)
.ok_or_else(|| {
crate::Error::NotSupported(format!(
"GL float render-to-PBO: F16 NCHW requires width divisible by 4, \
got {dst_w}; using CPU fallback"
))
})?;
(layout.surface_w, layout.surface_h)
}
FloatRenderPath::PboF32Nchw => packed_planar_layout(dst_w as u32, dst_h as u32, 3)
.map(|(w, h)| (w as usize, h as usize))
.ok_or_else(|| {
crate::Error::NotSupported(format!(
"GL float render-to-PBO: F32 NCHW requires width divisible by 4, \
got {dst_w}; using CPU fallback"
))
})?,
FloatRenderPath::PboF16Nhwc => packed_interleaved_layout(dst_w as u32, dst_h as u32)
.map(|(w, h)| (w as usize, h as usize))
.ok_or_else(|| {
crate::Error::NotSupported(format!(
"GL float render-to-PBO: F16 NHWC requires width divisible by 4, \
got {dst_w}; using CPU fallback"
))
})?,
_ => unreachable!(),
};
let layout = plan_pbo_readback(
dst.effective_row_stride().unwrap_or(packed_w * pixel_bytes),
dst.view_origin().is_some(),
dst.plane_offset().unwrap_or(0),
dst.capacity_bytes(),
packed_w,
packed_h,
pixel_bytes,
)?;
let (src_rect_uv, dst_rect_px, pad_color) =
float_crop_uniforms(&crop, src_w, src_h, dst_w, dst_h)?;
let program_id = match path {
FloatRenderPath::PboF32Nhwc => self.float_f32_nhwc_program.id,
FloatRenderPath::PboF16Nchw | FloatRenderPath::PboF32Nchw => {
self.float_f16_nchw_program.id
}
FloatRenderPath::PboF16Nhwc => self.float_nhwc_pack_program.id,
_ => unreachable!(),
};
let sampler_name: &CStr = match path {
FloatRenderPath::PboF32Nhwc => c"u_tex",
_ => c"src",
};
let render_tex_id = self.float_render_texture.id;
let src_tex_id = self.camera_normal_texture.id;
let src_filter = self.source_filter() as i32;
let feed = self.feed_float_src(src_u8, src_w, src_h, src_filter, convert_span)?;
let (src_rect_uv, src_extent) =
self.float_src_mapping_for_feed(feed, src_rect_uv, src_u8, src_w, src_h);
unsafe {
edgefirst_gl::gl::BindTexture(edgefirst_gl::gl::TEXTURE_2D, render_tex_id);
super::super::core::set_tex_filter(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::NEAREST,
);
let render_dims = (packed_w, packed_h, internal);
if self.float_render_tex_dims != render_dims {
edgefirst_gl::gl::TexImage2D(
edgefirst_gl::gl::TEXTURE_2D,
0,
internal as i32,
packed_w as i32,
packed_h as i32,
0,
client_fmt,
gl_type,
std::ptr::null(),
);
self.float_render_tex_dims = render_dims;
self.float_render_texture.invalidate_egl_binding();
}
self.convert_fbo.bind();
edgefirst_gl::gl::FramebufferTexture2D(
edgefirst_gl::gl::FRAMEBUFFER,
edgefirst_gl::gl::COLOR_ATTACHMENT0,
edgefirst_gl::gl::TEXTURE_2D,
render_tex_id,
0,
);
if let Err(fbo_status) = super::super::core::check_framebuffer_complete() {
edgefirst_gl::gl::BindFramebuffer(edgefirst_gl::gl::FRAMEBUFFER, 0);
return Err(crate::Error::NotSupported(format!(
"GL float render-to-PBO: FBO incomplete (0x{fbo_status:x}) for {path:?}; \
using CPU fallback"
)));
}
}
let dst_image_size = match path {
FloatRenderPath::PboF16Nchw | FloatRenderPath::PboF32Nchw => {
Some((dst_w as f32, dst_h as f32))
}
_ => None,
};
self.draw_float_quad(
program_id,
sampler_name,
src_tex_id,
packed_w as u32,
packed_h as u32,
src_rect_uv,
src_extent,
dst_rect_px,
pad_color,
dst_image_size,
)?;
unsafe {
read_float_pixels_into_pbo(
&layout,
dst_buffer_id,
packed_w,
packed_h,
client_fmt,
gl_type,
)?
}
convert_span.record("dst_feed", "pbo");
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) fn convert_float_to_zero_copy(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
path: FloatRenderPath,
rotation: crate::Rotation,
flip: Flip,
crop: ResolvedCrop,
convert_span: &tracing::Span,
) -> crate::Result<()> {
if rotation != Rotation::None || flip != Flip::None {
return Err(crate::Error::NotSupported(
"GL float render-to-DMA: rotation/flip not supported on the float path; \
using CPU fallback"
.to_string(),
));
}
let (src_u8, src_fmt) = dyn_to_u8_src(src)?;
if src_fmt != PixelFormat::Rgba {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: source format must be Rgba, got {src_fmt:?}; \
using CPU fallback"
)));
}
let src_w = src_u8.width().ok_or(Error::NotAnImage)?;
let src_h = src_u8.height().ok_or(Error::NotAnImage)?;
let dst_w = dst.width().ok_or(Error::NotAnImage)?;
let dst_h = dst.height().ok_or(Error::NotAnImage)?;
let (src_rect_uv, dst_rect_px, pad_color) =
float_crop_uniforms(&crop, src_w, src_h, dst_w, dst_h)?;
let src_tex_id = self.camera_normal_texture.id;
let src_filter = self.source_filter() as i32;
let feed = self.feed_float_src(src_u8, src_w, src_h, src_filter, convert_span)?;
let (src_rect_uv, src_extent) =
self.float_src_mapping_for_feed(feed, src_rect_uv, src_u8, src_w, src_h);
self.render_float_to_zero_copy_tail(
src_tex_id,
src_rect_uv,
src_extent,
dst_rect_px,
pad_color,
dst,
path,
convert_span,
)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn render_float_to_zero_copy_tail(
&mut self,
src_tex_id: u32,
src_rect_uv: [f32; 4],
src_extent: [f32; 4],
dst_rect_px: [f32; 4],
pad_color: [f32; 4],
dst: &mut TensorDyn,
path: FloatRenderPath,
convert_span: &tracing::Span,
) -> crate::Result<()> {
let dst_w = dst.width().ok_or(Error::NotAnImage)?;
let dst_h = dst.height().ok_or(Error::NotAnImage)?;
let dst_fmt = dst.format().ok_or(Error::NotAnImage)?;
let dst_dtype = dst.dtype();
if dst.memory() != TensorMemory::DmaBuf {
return Err(crate::Error::NotSupported(
"GL float render-to-DMA: dst is not a zero-copy GPU buffer; using CPU fallback"
.to_string(),
));
}
let (surface, program_id, dst_image_size) = match (path, dst_fmt, dst_dtype) {
(
FloatRenderPath::ZeroCopyF16Nchw,
PixelFormat::PlanarRgb | PixelFormat::PlanarRgba,
DType::F16,
)
| (
FloatRenderPath::ZeroCopyF32Nchw,
PixelFormat::PlanarRgb | PixelFormat::PlanarRgba,
DType::F32,
) => (
packed_planar_layout(dst_w as u32, dst_h as u32, dst_fmt.channels() as u32),
self.float_f16_nchw_program.id,
Some((dst_w as f32, dst_h as f32)),
),
(FloatRenderPath::ZeroCopyFloatNhwc, PixelFormat::Rgb, DType::F16 | DType::F32) => (
packed_interleaved_layout(dst_w as u32, dst_h as u32),
self.float_nhwc_pack_program.id,
None,
),
(FloatRenderPath::ZeroCopyFloatRgba, PixelFormat::Rgba, DType::F16 | DType::F32) => (
Some((dst_w as u32, dst_h as u32)),
self.float_rgba_program.id,
None,
),
_ => {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: {path:?} does not render a {dst_fmt:?}/{dst_dtype:?} \
destination; using CPU fallback"
)));
}
};
let (surface_w, surface_h) = surface.ok_or_else(|| {
crate::Error::NotSupported(format!(
"GL float render-to-DMA: {dst_fmt:?} packing requires width divisible by 4, \
got {dst_w}; using CPU fallback"
))
})?;
let packed = match dst_dtype {
DType::F16 => super::super::platform::PackedImportFormat::Rgba16161616F,
DType::F32 => super::super::platform::PackedImportFormat::Rgba32323232F,
other => {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: dst dtype must be F16 or F32, got {other:?}; \
using CPU fallback"
)));
}
};
match dst_dtype {
DType::F16 => {
let dst_t = dst.as_typed_mut::<half::f16>().expect("dtype checked");
self.render_float_dst(
dst_t,
dst_fmt,
surface_w,
surface_h,
packed,
program_id,
src_tex_id,
src_rect_uv,
src_extent,
dst_rect_px,
pad_color,
dst_image_size,
convert_span,
)
}
_ => {
let dst_t = dst.as_typed_mut::<f32>().expect("dtype checked");
self.render_float_dst(
dst_t,
dst_fmt,
surface_w,
surface_h,
packed,
program_id,
src_tex_id,
src_rect_uv,
src_extent,
dst_rect_px,
pad_color,
dst_image_size,
convert_span,
)
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_float_dst<T>(
&mut self,
dst: &Tensor<T>,
dst_fmt: PixelFormat,
surface_w: u32,
surface_h: u32,
packed: super::super::platform::PackedImportFormat,
program_id: u32,
src_tex_id: u32,
src_rect_uv: [f32; 4],
src_extent: [f32; 4],
dst_rect_px: [f32; 4],
pad_color: [f32; 4],
dst_image_size: Option<(f32, f32)>,
convert_span: &tracing::Span,
) -> crate::Result<()>
where
T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync + edgefirst_tensor::Element,
{
self.convert_fbo.bind();
let dest_egl = self.get_or_create_egl_image_rgb(
dst,
dst_fmt,
surface_w as usize,
surface_h as usize,
packed,
)?;
unsafe {
match self.cached_dst_renderbuffer(dst, dst_fmt) {
Some(rbo) => {
edgefirst_gl::gl::BindRenderbuffer(edgefirst_gl::gl::RENDERBUFFER, rbo);
edgefirst_gl::gl::FramebufferRenderbuffer(
edgefirst_gl::gl::FRAMEBUFFER,
edgefirst_gl::gl::COLOR_ATTACHMENT0,
edgefirst_gl::gl::RENDERBUFFER,
rbo,
);
}
None => {
edgefirst_gl::gl::ActiveTexture(edgefirst_gl::gl::TEXTURE0);
edgefirst_gl::gl::BindTexture(
edgefirst_gl::gl::TEXTURE_2D,
self.render_texture.id,
);
super::super::core::set_tex_filter(
edgefirst_gl::gl::TEXTURE_2D,
edgefirst_gl::gl::NEAREST,
);
super::super::platform::Platform::attach_tex_image_2d(
&self.gl_context,
dest_egl,
)?;
edgefirst_gl::gl::FramebufferTexture2D(
edgefirst_gl::gl::FRAMEBUFFER,
edgefirst_gl::gl::COLOR_ATTACHMENT0,
edgefirst_gl::gl::TEXTURE_2D,
self.render_texture.id,
0,
);
self.render_texture.invalidate_egl_binding();
}
}
if let Err(fbo_status) = super::super::core::check_framebuffer_complete() {
edgefirst_gl::gl::BindFramebuffer(edgefirst_gl::gl::FRAMEBUFFER, 0);
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: FBO incomplete (0x{fbo_status:x}) for the \
{packed:?} import; using CPU fallback"
)));
}
check_gl_error(function!(), line!())?;
}
self.draw_float_quad(
program_id,
c"src",
src_tex_id,
surface_w,
surface_h,
src_rect_uv,
src_extent,
dst_rect_px,
pad_color,
dst_image_size,
)?;
unsafe {
finish_via_fence();
}
check_gl_error(function!(), line!())?;
convert_span.record("dst_feed", "zero_copy");
Ok(())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{float_pbo_buffer_id, packed_interleaved_layout, packed_planar_layout};
use crate::Error;
#[test]
fn packed_interleaved_layout_takes_whole_texels_only() {
assert_eq!(packed_interleaved_layout(640, 480), Some((480, 480)));
assert_eq!(packed_interleaved_layout(4, 1), Some((3, 1)));
for w in [1u32, 2, 3, 5, 6, 7] {
assert_eq!(
packed_interleaved_layout(w, 16),
None,
"{w}x3 is not a whole number of four-element texels"
);
}
assert_eq!(packed_planar_layout(640, 480, 3), Some((160, 1440)));
assert_eq!(packed_planar_layout(6, 16, 3), None);
}
#[test]
fn pbo_buffer_id_rejects_non_pbo_tensor() {
let t = edgefirst_tensor::Tensor::<f32>::image(
4,
4,
edgefirst_tensor::PixelFormat::Rgb,
Some(edgefirst_tensor::TensorMemory::Mem),
edgefirst_tensor::CpuAccess::ReadWrite,
)
.unwrap();
let err = float_pbo_buffer_id(&t).unwrap_err();
assert!(
matches!(err, Error::OpenGl(_)),
"expected OpenGl error, got {err:?}"
);
}
}