use std::ffi::{c_void, CStr};
use super::super::core::float_crop_uniforms;
use super::super::platform::GlPlatform;
use super::{check_gl_error, dyn_to_u8_src, GLProcessorST};
use crate::{Error, Flip, ResolvedCrop, Rotation};
use edgefirst_tensor::{PixelFormat, TensorDyn, TensorTrait};
pub(in super::super) use super::super::float_dispatch::{classify_float_render, FloatRenderPath};
pub(in super::super) fn float_render_support(
is_vivante: bool,
f32_ext: bool,
f16_ext: bool,
) -> crate::RenderDtypeSupport {
if is_vivante {
return crate::RenderDtypeSupport {
f32: false,
f16: false,
};
}
crate::RenderDtypeSupport {
f32: f32_ext,
f16: f16_ext,
}
}
pub(in super::super) fn dma_f16_packed_layout(w: u32, h: u32) -> Option<(u32, u32, u32)> {
let layout = edgefirst_tensor::packed_rgba16f_layout(
PixelFormat::PlanarRgb,
edgefirst_tensor::DType::F16,
w as usize,
h as usize,
)?;
Some((
layout.surface_w as u32,
layout.surface_h as u32,
layout.pitch as u32,
))
}
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,
{
let pbo = t.as_pbo().ok_or_else(|| {
crate::Error::OpenGl("convert_float_to_pbo: dst is not a PBO tensor".to_string())
})?;
if pbo.is_mapped() {
return Err(crate::Error::OpenGl(
"Cannot convert to a mapped PBO tensor".to_string(),
));
}
Ok(pbo.buffer_id())
}
pub(super) unsafe fn finish_via_fence() {
const TIMEOUT_NS: u64 = 1_000_000_000;
let sync = gls::gl::FenceSync(gls::gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
if sync.is_null() {
gls::gl::Finish();
return;
}
let status = gls::gl::ClientWaitSync(sync, gls::gl::SYNC_FLUSH_COMMANDS_BIT, TIMEOUT_NS);
gls::gl::DeleteSync(sync);
match status {
s if s == gls::gl::ALREADY_SIGNALED || s == gls::gl::CONDITION_SATISFIED => {}
_ => gls::gl::Finish(),
}
}
impl GLProcessorST {
pub(super) fn upload_float_src(
&mut self,
src_tex_id: u32,
src_w: usize,
src_h: usize,
src_filter: i32,
src_pbo_id: Option<u32>,
src_cpu_pixels: Option<&[u8]>,
) {
unsafe {
gls::gl::ActiveTexture(gls::gl::TEXTURE0);
gls::gl::BindTexture(gls::gl::TEXTURE_2D, src_tex_id);
gls::gl::TexParameteri(gls::gl::TEXTURE_2D, gls::gl::TEXTURE_MIN_FILTER, src_filter);
gls::gl::TexParameteri(gls::gl::TEXTURE_2D, gls::gl::TEXTURE_MAG_FILTER, src_filter);
gls::gl::TexParameteri(
gls::gl::TEXTURE_2D,
gls::gl::TEXTURE_WRAP_S,
gls::gl::CLAMP_TO_EDGE as i32,
);
gls::gl::TexParameteri(
gls::gl::TEXTURE_2D,
gls::gl::TEXTURE_WRAP_T,
gls::gl::CLAMP_TO_EDGE as i32,
);
for (swizzle, comp) in [
(gls::gl::TEXTURE_SWIZZLE_R, gls::gl::RED),
(gls::gl::TEXTURE_SWIZZLE_G, gls::gl::GREEN),
(gls::gl::TEXTURE_SWIZZLE_B, gls::gl::BLUE),
(gls::gl::TEXTURE_SWIZZLE_A, gls::gl::ALPHA),
] {
gls::gl::TexParameteri(gls::gl::TEXTURE_2D, swizzle, comp as i32);
}
if let Some(buffer_id) = src_pbo_id {
gls::gl::BindBuffer(gls::gl::PIXEL_UNPACK_BUFFER, buffer_id);
let cache = &mut self.camera_normal_texture;
let needs_alloc = cache.target != gls::gl::TEXTURE_2D
|| cache.width != src_w
|| cache.height != src_h
|| cache.format != gls::gl::RGBA;
if needs_alloc {
gls::gl::TexImage2D(
gls::gl::TEXTURE_2D,
0,
gls::gl::RGBA as i32,
src_w as i32,
src_h as i32,
0,
gls::gl::RGBA,
gls::gl::UNSIGNED_BYTE,
std::ptr::null(),
);
cache.target = gls::gl::TEXTURE_2D;
cache.width = src_w;
cache.height = src_h;
cache.format = gls::gl::RGBA;
cache.invalidate_egl_binding();
} else {
gls::gl::TexSubImage2D(
gls::gl::TEXTURE_2D,
0,
0,
0,
src_w as i32,
src_h as i32,
gls::gl::RGBA,
gls::gl::UNSIGNED_BYTE,
std::ptr::null(),
);
}
gls::gl::BindBuffer(gls::gl::PIXEL_UNPACK_BUFFER, 0);
} else {
let pixels = src_cpu_pixels.expect("non-PBO source mapped by caller");
self.camera_normal_texture.update_texture(
gls::gl::TEXTURE_2D,
src_w,
src_h,
gls::gl::RGBA,
pixels,
);
}
}
}
#[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],
dst_rect_px: [f32; 4],
pad_color: [f32; 3],
dst_image_size: Option<(f32, f32)>,
) -> crate::Result<()> {
unsafe {
gls::gl::Viewport(0, 0, packed_w as i32, packed_h as i32);
gls::gl::UseProgram(program_id);
gls::gl::ActiveTexture(gls::gl::TEXTURE0);
gls::gl::BindTexture(gls::gl::TEXTURE_2D, src_tex_id);
let loc_sampler = gls::gl::GetUniformLocation(program_id, sampler_name.as_ptr());
gls::gl::Uniform1i(loc_sampler, 0);
let loc_src_rect = gls::gl::GetUniformLocation(program_id, c"src_rect_uv".as_ptr());
gls::gl::Uniform4f(
loc_src_rect,
src_rect_uv[0],
src_rect_uv[1],
src_rect_uv[2],
src_rect_uv[3],
);
let loc_dst_rect = gls::gl::GetUniformLocation(program_id, c"dst_rect_px".as_ptr());
gls::gl::Uniform4f(
loc_dst_rect,
dst_rect_px[0],
dst_rect_px[1],
dst_rect_px[2],
dst_rect_px[3],
);
let loc_pad = gls::gl::GetUniformLocation(program_id, c"pad_color".as_ptr());
gls::gl::Uniform3f(loc_pad, pad_color[0], pad_color[1], pad_color[2]);
if let Some((w, h)) = dst_image_size {
let loc_size = gls::gl::GetUniformLocation(program_id, c"dst_image_size".as_ptr());
gls::gl::Uniform2f(loc_size, w, h);
}
check_gl_error(function!(), line!())?;
gls::gl::BindBuffer(gls::gl::ARRAY_BUFFER, self.vertex_buffer.id);
gls::gl::EnableVertexAttribArray(self.vertex_buffer.buffer_index);
let 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, ];
gls::gl::BufferData(
gls::gl::ARRAY_BUFFER,
(size_of::<f32>() * quad_pos.len()) as isize,
quad_pos.as_ptr() as *const c_void,
gls::gl::DYNAMIC_DRAW,
);
gls::gl::BindBuffer(gls::gl::ARRAY_BUFFER, self.texture_buffer.id);
gls::gl::EnableVertexAttribArray(self.texture_buffer.buffer_index);
let quad_uv: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
gls::gl::BufferData(
gls::gl::ARRAY_BUFFER,
(size_of::<f32>() * quad_uv.len()) as isize,
quad_uv.as_ptr() as *const c_void,
gls::gl::DYNAMIC_DRAW,
);
let quad_index: [u32; 4] = [0, 1, 2, 3];
gls::gl::DrawElements(
gls::gl::TRIANGLE_FAN,
quad_index.len() as i32,
gls::gl::UNSIGNED_INT,
quad_index.as_ptr() as *const c_void,
);
check_gl_error(function!(), line!())?;
}
Ok(())
}
#[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,
) -> crate::Result<()> {
let (internal, client_fmt, gl_type) = match path {
FloatRenderPath::PboF32Nhwc => (gls::gl::R32F, gls::gl::RED, gls::gl::FLOAT),
FloatRenderPath::PboF16Nchw => (gls::gl::RGBA16F, gls::gl::RGBA, gls::gl::HALF_FLOAT),
FloatRenderPath::ZeroCopyF16Nchw | FloatRenderPath::None => {
return Err(crate::Error::NotSupported(
"GL float render-to-PBO: only PBO F32 NHWC / F16 NCHW are implemented; \
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 {
TensorDyn::F32(t) => float_pbo_buffer_id(t)?,
TensorDyn::F16(t) => float_pbo_buffer_id(t)?,
other => {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-PBO: dst dtype must be F32 or F16, got {:?}",
other.dtype()
)));
}
};
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)
}
_ => unreachable!(),
};
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 => self.float_f16_nchw_program.id,
_ => unreachable!(),
};
let sampler_name: &CStr = match path {
FloatRenderPath::PboF32Nhwc => c"u_tex",
FloatRenderPath::PboF16Nchw => c"src",
_ => unreachable!(),
};
let render_tex_id = self.float_render_texture.id;
let src_tex_id = self.camera_normal_texture.id;
let src_pbo_id = src_u8.as_pbo().map(|p| p.buffer_id());
let src_cpu_pixels = if src_pbo_id.is_none() {
Some(src_u8.map()?)
} else {
None
};
let src_filter = gls::gl::LINEAR as i32;
self.upload_float_src(
src_tex_id,
src_w,
src_h,
src_filter,
src_pbo_id,
src_cpu_pixels.as_deref(),
);
unsafe {
gls::gl::BindTexture(gls::gl::TEXTURE_2D, render_tex_id);
super::super::core::set_tex_filter(gls::gl::TEXTURE_2D, gls::gl::NEAREST);
let render_dims = (packed_w, packed_h, internal);
if self.float_render_tex_dims != render_dims {
gls::gl::TexImage2D(
gls::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();
gls::gl::FramebufferTexture2D(
gls::gl::FRAMEBUFFER,
gls::gl::COLOR_ATTACHMENT0,
gls::gl::TEXTURE_2D,
render_tex_id,
0,
);
if let Err(fbo_status) = super::super::core::check_framebuffer_complete() {
gls::gl::BindFramebuffer(gls::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 => 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,
dst_rect_px,
pad_color,
dst_image_size,
)?;
unsafe {
gls::gl::BindBuffer(gls::gl::PIXEL_PACK_BUFFER, dst_buffer_id);
gls::gl::ReadBuffer(gls::gl::COLOR_ATTACHMENT0);
gls::gl::ReadPixels(
0,
0,
packed_w as i32,
packed_h as i32,
client_fmt,
gl_type,
std::ptr::null_mut(),
);
gls::gl::BindBuffer(gls::gl::PIXEL_PACK_BUFFER, 0);
finish_via_fence();
}
check_gl_error(function!(), line!())?;
Ok(())
}
pub(super) fn convert_float_to_zero_copy(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
rotation: crate::Rotation,
flip: Flip,
crop: ResolvedCrop,
) -> 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_pbo_id = src_u8.as_pbo().map(|p| p.buffer_id());
let src_cpu_pixels = if src_pbo_id.is_none() {
Some(src_u8.map()?)
} else {
None
};
let src_filter = gls::gl::LINEAR as i32;
self.upload_float_src(
src_tex_id,
src_w,
src_h,
src_filter,
src_pbo_id,
src_cpu_pixels.as_deref(),
);
drop(src_cpu_pixels);
self.render_float_to_zero_copy_tail(src_tex_id, src_rect_uv, dst_rect_px, pad_color, dst)
}
pub(super) fn render_float_to_zero_copy_tail(
&mut self,
src_tex_id: u32,
src_rect_uv: [f32; 4],
dst_rect_px: [f32; 4],
pad_color: [f32; 3],
dst: &mut TensorDyn,
) -> 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)?;
if dst_fmt != PixelFormat::PlanarRgb {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: dst format must be PlanarRgb, got {dst_fmt:?}; \
using CPU fallback"
)));
}
let dst_f16 = match dst {
TensorDyn::F16(t) => t,
other => {
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: dst dtype must be F16, got {:?}; using CPU fallback",
other.dtype()
)));
}
};
if dst_f16.memory() != edgefirst_tensor::TensorMemory::Dma {
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_w, surface_h, _pitch) = dma_f16_packed_layout(dst_w as u32, dst_h as u32)
.ok_or_else(|| {
crate::Error::NotSupported(format!(
"GL float render-to-DMA: F16 NCHW requires width divisible by 4, \
got {dst_w}; using CPU fallback"
))
})?;
let program_id = self.float_f16_nchw_program.id;
self.convert_fbo.bind();
let dest_egl = self.get_or_create_egl_image_rgb(
dst_f16,
PixelFormat::PlanarRgb,
surface_w as usize,
surface_h as usize,
super::super::platform::PackedImportFormat::Rgba16161616F,
)?;
unsafe {
match self.cached_dst_renderbuffer(dst_f16, PixelFormat::PlanarRgb) {
Some(rbo) => {
gls::gl::BindRenderbuffer(gls::gl::RENDERBUFFER, rbo);
gls::gl::FramebufferRenderbuffer(
gls::gl::FRAMEBUFFER,
gls::gl::COLOR_ATTACHMENT0,
gls::gl::RENDERBUFFER,
rbo,
);
}
None => {
gls::gl::ActiveTexture(gls::gl::TEXTURE0);
gls::gl::BindTexture(gls::gl::TEXTURE_2D, self.render_texture.id);
super::super::core::set_tex_filter(gls::gl::TEXTURE_2D, gls::gl::NEAREST);
super::super::platform::Platform::attach_tex_image_2d(
&self.gl_context,
dest_egl,
)?;
gls::gl::FramebufferTexture2D(
gls::gl::FRAMEBUFFER,
gls::gl::COLOR_ATTACHMENT0,
gls::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() {
gls::gl::BindFramebuffer(gls::gl::FRAMEBUFFER, 0);
return Err(crate::Error::NotSupported(format!(
"GL float render-to-DMA: FBO incomplete (0x{fbo_status:x}) for the RGBA16F \
dma-buf 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,
dst_rect_px,
pad_color,
Some((dst_w as f32, dst_h as f32)),
)?;
unsafe {
finish_via_fence();
}
check_gl_error(function!(), line!())?;
Ok(())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::float_pbo_buffer_id;
use crate::Error;
#[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),
)
.unwrap();
let err = float_pbo_buffer_id(&t).unwrap_err();
assert!(
matches!(err, Error::OpenGl(_)),
"expected OpenGl error, got {err:?}"
);
}
}