blob: c6754f3e7f6cc41d704fc9c35ec7bfe73503b692 [file]
use crate::api::icd::*;
use crate::api::types::*;
use crate::api::util::*;
use crate::core::context::*;
use crate::core::device::*;
use crate::core::format::*;
use crate::core::queue::*;
use crate::core::util::cl_mem_type_to_texture_target;
use crate::impl_cl_type_trait;
use mesa_rust::pipe::context::*;
use mesa_rust::pipe::resource::*;
use mesa_rust::pipe::screen::ResourceType;
use mesa_rust::pipe::transfer::*;
use mesa_rust_gen::*;
use mesa_rust_util::math::*;
use mesa_rust_util::properties::Properties;
use rusticl_opencl_gen::*;
use std::cmp;
use std::collections::HashMap;
use std::convert::TryInto;
use std::mem::size_of;
use std::ops::AddAssign;
use std::os::raw::c_void;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::MutexGuard;
struct MappingTransfer {
tx: PipeTransfer,
shadow: Option<PipeResource>,
pending: u32,
}
impl MappingTransfer {
fn new(tx: PipeTransfer, shadow: Option<PipeResource>) -> Self {
MappingTransfer {
tx: tx,
shadow: shadow,
pending: 1,
}
}
}
struct Mappings {
tx: HashMap<Arc<Device>, MappingTransfer>,
maps: HashMap<*mut c_void, u32>,
}
impl Mappings {
fn new() -> Mutex<Self> {
Mutex::new(Mappings {
tx: HashMap::new(),
maps: HashMap::new(),
})
}
fn mark_pending(&mut self, dev: &Device) {
self.tx.get_mut(dev).unwrap().pending += 1;
}
fn unmark_pending(&mut self, dev: &Device) {
if let Some(tx) = self.tx.get_mut(dev) {
tx.pending -= 1;
}
}
fn increase_ref(&mut self, dev: &Device, ptr: *mut c_void) -> bool {
let res = self.maps.is_empty();
*self.maps.entry(ptr).or_default() += 1;
self.unmark_pending(dev);
res
}
fn decrease_ref(&mut self, ptr: *mut c_void, dev: &Device) -> (bool, Option<&PipeResource>) {
if let Some(r) = self.maps.get_mut(&ptr) {
*r -= 1;
if *r == 0 {
self.maps.remove(&ptr);
}
if self.maps.is_empty() {
let shadow = self.tx.get(dev).and_then(|tx| tx.shadow.as_ref());
return (true, shadow);
}
}
(false, None)
}
fn clean_up_tx(&mut self, dev: &Device, ctx: &PipeContext) {
if self.maps.is_empty() {
if let Some(tx) = self.tx.get(dev) {
if tx.pending == 0 {
self.tx.remove(dev).unwrap().tx.with_ctx(ctx);
}
}
}
}
}
#[repr(C)]
pub struct Mem {
pub base: CLObjectBase<CL_INVALID_MEM_OBJECT>,
pub context: Arc<Context>,
pub parent: Option<Arc<Mem>>,
pub mem_type: cl_mem_object_type,
pub flags: cl_mem_flags,
pub size: usize,
pub offset: usize,
pub host_ptr: *mut c_void,
pub image_format: cl_image_format,
pub image_desc: cl_image_desc,
pub image_elem_size: u8,
pub props: Vec<cl_mem_properties>,
pub cbs: Mutex<Vec<Box<dyn Fn(cl_mem)>>>,
res: Option<HashMap<Arc<Device>, Arc<PipeResource>>>,
maps: Mutex<Mappings>,
}
impl_cl_type_trait!(cl_mem, Mem, CL_INVALID_MEM_OBJECT);
pub trait CLImageDescInfo {
fn type_info(&self) -> (u8, bool);
fn pixels(&self) -> usize;
fn bx(&self) -> CLResult<pipe_box>;
fn row_pitch(&self) -> CLResult<u32>;
fn slice_pitch(&self) -> CLResult<u32>;
fn size(&self) -> CLVec<usize>;
fn api_size(&self) -> CLVec<usize>;
fn dims(&self) -> u8 {
self.type_info().0
}
fn has_slice(&self) -> bool {
self.dims() == 3 || self.is_array()
}
fn is_array(&self) -> bool {
self.type_info().1
}
}
impl CLImageDescInfo for cl_image_desc {
fn type_info(&self) -> (u8, bool) {
match self.image_type {
CL_MEM_OBJECT_IMAGE1D | CL_MEM_OBJECT_IMAGE1D_BUFFER => (1, false),
CL_MEM_OBJECT_IMAGE1D_ARRAY => (1, true),
CL_MEM_OBJECT_IMAGE2D => (2, false),
CL_MEM_OBJECT_IMAGE2D_ARRAY => (2, true),
CL_MEM_OBJECT_IMAGE3D => (3, false),
_ => panic!("unknown image_type {:x}", self.image_type),
}
}
fn pixels(&self) -> usize {
let mut res = self.image_width;
let dims = self.dims();
if dims > 1 {
res *= self.image_height;
}
if dims > 2 {
res *= self.image_depth;
}
if self.is_array() {
res *= self.image_array_size;
}
res
}
fn size(&self) -> CLVec<usize> {
let mut depth = if self.is_array() {
self.image_array_size
} else {
self.image_depth
};
let height = cmp::max(self.image_height, 1);
depth = cmp::max(depth, 1);
CLVec::new([self.image_width, height, depth])
}
fn api_size(&self) -> CLVec<usize> {
let mut size = self.size();
if self.is_array() && self.dims() == 1 {
size[1] = size[2];
size[2] = 1;
}
size
}
fn bx(&self) -> CLResult<pipe_box> {
let size = self.size();
Ok(pipe_box {
x: 0,
y: 0,
z: 0,
width: size[0].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
height: size[1].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
depth: size[2].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
})
}
fn row_pitch(&self) -> CLResult<u32> {
self.image_row_pitch
.try_into()
.map_err(|_| CL_OUT_OF_HOST_MEMORY)
}
fn slice_pitch(&self) -> CLResult<u32> {
self.image_slice_pitch
.try_into()
.map_err(|_| CL_OUT_OF_HOST_MEMORY)
}
}
fn sw_copy(
src: *const c_void,
dst: *mut c_void,
region: &CLVec<usize>,
src_origin: &CLVec<usize>,
src_row_pitch: usize,
src_slice_pitch: usize,
dst_origin: &CLVec<usize>,
dst_row_pitch: usize,
dst_slice_pitch: usize,
pixel_size: u8,
) {
for z in 0..region[2] {
for y in 0..region[1] {
unsafe {
ptr::copy_nonoverlapping(
src.add((*src_origin + [0, y, z]) * [1, src_row_pitch, src_slice_pitch]),
dst.add((*dst_origin + [0, y, z]) * [1, dst_row_pitch, dst_slice_pitch]),
region[0] * pixel_size as usize,
)
};
}
}
}
fn create_box(
origin: &CLVec<usize>,
region: &CLVec<usize>,
tex_type: cl_mem_object_type,
) -> CLResult<pipe_box> {
let mut y = 1;
let mut z = 2;
// array slice belongs to z/depth
if tex_type == CL_MEM_OBJECT_IMAGE1D_ARRAY {
(z, y) = (y, z);
}
Ok(pipe_box {
x: origin[0].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
y: origin[y].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
z: origin[z].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
width: region[0].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
height: region[y].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
depth: region[z].try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
})
}
fn buffer_offset_size(
origin: &CLVec<usize>,
region: &CLVec<usize>,
row_pitch: usize,
slice_pitch: usize,
) -> (usize, usize) {
let pitch = [1, row_pitch, slice_pitch];
(*origin * pitch, *region * pitch)
}
impl Mem {
pub fn new_buffer(
context: Arc<Context>,
flags: cl_mem_flags,
size: usize,
host_ptr: *mut c_void,
props: Vec<cl_mem_properties>,
) -> CLResult<Arc<Mem>> {
let res_type = if bit_check(flags, CL_MEM_ALLOC_HOST_PTR) {
ResourceType::Staging
} else {
ResourceType::Normal
};
let buffer = context.create_buffer(
size,
host_ptr,
bit_check(flags, CL_MEM_COPY_HOST_PTR),
res_type,
)?;
let host_ptr = if bit_check(flags, CL_MEM_USE_HOST_PTR) {
host_ptr
} else {
ptr::null_mut()
};
Ok(Arc::new(Self {
base: CLObjectBase::new(),
context: context,
parent: None,
mem_type: CL_MEM_OBJECT_BUFFER,
flags: flags,
size: size,
offset: 0,
host_ptr: host_ptr,
image_format: cl_image_format::default(),
image_desc: cl_image_desc::default(),
image_elem_size: 0,
props: props,
cbs: Mutex::new(Vec::new()),
res: Some(buffer),
maps: Mappings::new(),
}))
}
pub fn new_sub_buffer(
parent: Arc<Mem>,
flags: cl_mem_flags,
offset: usize,
size: usize,
) -> Arc<Mem> {
let host_ptr = if parent.host_ptr.is_null() {
ptr::null_mut()
} else {
unsafe { parent.host_ptr.add(offset) }
};
Arc::new(Self {
base: CLObjectBase::new(),
context: parent.context.clone(),
parent: Some(parent),
mem_type: CL_MEM_OBJECT_BUFFER,
flags: flags,
size: size,
offset: offset,
host_ptr: host_ptr,
image_format: cl_image_format::default(),
image_desc: cl_image_desc::default(),
image_elem_size: 0,
props: Vec::new(),
cbs: Mutex::new(Vec::new()),
res: None,
maps: Mappings::new(),
})
}
pub fn new_image(
context: Arc<Context>,
parent: Option<Arc<Mem>>,
mem_type: cl_mem_object_type,
flags: cl_mem_flags,
image_format: &cl_image_format,
mut image_desc: cl_image_desc,
image_elem_size: u8,
host_ptr: *mut c_void,
props: Vec<cl_mem_properties>,
) -> CLResult<Arc<Mem>> {
// we have to sanitize the image_desc a little for internal use
let api_image_desc = image_desc;
let dims = image_desc.dims();
let is_array = image_desc.is_array();
if dims < 3 {
image_desc.image_depth = 1;
}
if dims < 2 {
image_desc.image_height = 1;
}
if !is_array {
image_desc.image_array_size = 1;
}
let res_type = if bit_check(flags, CL_MEM_ALLOC_HOST_PTR) {
ResourceType::Staging
} else {
ResourceType::Normal
};
let texture = if parent.is_none() {
Some(context.create_texture(
&image_desc,
image_format,
host_ptr,
bit_check(flags, CL_MEM_COPY_HOST_PTR),
res_type,
)?)
} else {
None
};
let host_ptr = if bit_check(flags, CL_MEM_USE_HOST_PTR) {
host_ptr
} else {
ptr::null_mut()
};
Ok(Arc::new(Self {
base: CLObjectBase::new(),
context: context,
parent: parent,
mem_type: mem_type,
flags: flags,
size: image_desc.pixels() * image_format.pixel_size().unwrap() as usize,
offset: 0,
host_ptr: host_ptr,
image_format: *image_format,
image_desc: api_image_desc,
image_elem_size: image_elem_size,
props: props,
cbs: Mutex::new(Vec::new()),
res: texture,
maps: Mappings::new(),
}))
}
pub fn is_buffer(&self) -> bool {
self.mem_type == CL_MEM_OBJECT_BUFFER
}
fn tx_raw(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
mut offset: usize,
size: usize,
rw: RWFlags,
) -> CLResult<PipeTransfer> {
let b = self.to_parent(&mut offset);
let r = b.get_res()?.get(&q.device).unwrap();
assert!(self.is_buffer());
Ok(ctx.buffer_map(
r,
offset.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
size.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
rw,
ResourceMapType::Normal,
))
}
fn tx_raw_async(
&self,
q: &Arc<Queue>,
rw: RWFlags,
) -> CLResult<(PipeTransfer, Option<PipeResource>)> {
let mut offset = 0;
let b = self.to_parent(&mut offset);
let r = b.get_res()?.get(&q.device).unwrap();
let size = self.size.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?;
let ctx = q.device.helper_ctx();
assert!(self.is_buffer());
// don't bother mapping directly if it's not UMA or a staging buffer
let tx = if q.device.unified_memory() || bit_check(b.flags, CL_MEM_ALLOC_HOST_PTR) {
ctx.buffer_map_directly(
r,
offset.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
size,
rw,
)
} else {
None
};
if let Some(tx) = tx {
Ok((tx, None))
} else {
let shadow = q
.device
.screen()
.resource_create_buffer(size as u32, ResourceType::Staging)
.ok_or(CL_OUT_OF_RESOURCES)?;
let tx = ctx.buffer_map_coherent(&shadow, 0, size, rw);
Ok((tx, Some(shadow)))
}
}
fn tx<'a>(
&self,
q: &Arc<Queue>,
ctx: &'a PipeContext,
offset: usize,
size: usize,
rw: RWFlags,
) -> CLResult<GuardedPipeTransfer<'a>> {
Ok(self.tx_raw(q, ctx, offset, size, rw)?.with_ctx(ctx))
}
fn tx_image_raw(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
bx: &pipe_box,
rw: RWFlags,
) -> CLResult<PipeTransfer> {
assert!(!self.is_buffer());
let r = self.get_res()?.get(&q.device).unwrap();
Ok(ctx.texture_map(r, bx, rw, ResourceMapType::Normal))
}
fn tx_image_raw_async(
&self,
q: &Arc<Queue>,
bx: &pipe_box,
rw: RWFlags,
) -> CLResult<(PipeTransfer, Option<PipeResource>)> {
assert!(!self.is_buffer());
let r = self.get_res()?.get(&q.device).unwrap();
let ctx = q.device.helper_ctx();
// don't bother mapping directly if it's not staging
let tx = if bit_check(self.flags, CL_MEM_ALLOC_HOST_PTR) {
ctx.texture_map_directly(r, bx, rw)
} else {
None
};
if let Some(tx) = tx {
Ok((tx, None))
} else {
let shadow = q
.device
.screen()
.resource_create_texture(
r.width(),
r.height(),
r.depth(),
r.array_size(),
cl_mem_type_to_texture_target(self.image_desc.image_type),
self.image_format.to_pipe_format().unwrap(),
ResourceType::Staging,
)
.ok_or(CL_OUT_OF_RESOURCES)?;
let tx = ctx.texture_map_coherent(&shadow, bx, rw);
Ok((tx, Some(shadow)))
}
}
fn tx_image<'a>(
&self,
q: &Arc<Queue>,
ctx: &'a PipeContext,
bx: &pipe_box,
rw: RWFlags,
) -> CLResult<GuardedPipeTransfer<'a>> {
Ok(self.tx_image_raw(q, ctx, bx, rw)?.with_ctx(ctx))
}
pub fn has_same_parent(&self, other: &Self) -> bool {
let a = self.parent.as_ref().map_or(self, |p| p);
let b = other.parent.as_ref().map_or(other, |p| p);
ptr::eq(a, b)
}
fn get_res(&self) -> CLResult<&HashMap<Arc<Device>, Arc<PipeResource>>> {
self.parent
.as_ref()
.map_or(self, |p| p.as_ref())
.res
.as_ref()
.ok_or(CL_OUT_OF_HOST_MEMORY)
}
pub fn get_res_of_dev(&self, dev: &Arc<Device>) -> CLResult<&Arc<PipeResource>> {
Ok(self.get_res()?.get(dev).unwrap())
}
fn to_parent<'a>(&'a self, offset: &mut usize) -> &'a Self {
if let Some(parent) = &self.parent {
offset.add_assign(self.offset);
parent
} else {
self
}
}
fn has_user_shadow_buffer(&self, d: &Device) -> CLResult<bool> {
let r = self.get_res()?.get(d).unwrap();
Ok(!r.is_user && bit_check(self.flags, CL_MEM_USE_HOST_PTR))
}
pub fn read_to_user(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
offset: usize,
ptr: *mut c_void,
size: usize,
) -> CLResult<()> {
assert!(self.is_buffer());
let tx = self.tx(q, ctx, offset, size, RWFlags::RD)?;
unsafe {
ptr::copy_nonoverlapping(tx.ptr(), ptr, size);
}
Ok(())
}
pub fn write_from_user(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
mut offset: usize,
ptr: *const c_void,
size: usize,
) -> CLResult<()> {
assert!(self.is_buffer());
let b = self.to_parent(&mut offset);
let r = b.get_res()?.get(&q.device).unwrap();
ctx.buffer_subdata(
r,
offset.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
ptr,
size.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
);
Ok(())
}
pub fn copy_to(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
dst: &Arc<Mem>,
mut src_origin: CLVec<usize>,
mut dst_origin: CLVec<usize>,
region: &CLVec<usize>,
) -> CLResult<()> {
let src = self.to_parent(&mut src_origin[0]);
let dst = dst.to_parent(&mut dst_origin[0]);
let src_res = src.get_res()?.get(&q.device).unwrap();
let dst_res = dst.get_res()?.get(&q.device).unwrap();
if self.is_buffer() && !dst.is_buffer() || !self.is_buffer() && dst.is_buffer() {
let tx_src;
let tx_dst;
if self.is_buffer() {
let bpp = dst.image_format.pixel_size().unwrap() as usize;
tx_src = self.tx(q, ctx, src_origin[0], region.pixels() * bpp, RWFlags::RD)?;
tx_dst = dst.tx_image(
q,
ctx,
&create_box(&dst_origin, region, dst.mem_type)?,
RWFlags::WR,
)?;
sw_copy(
tx_src.ptr(),
tx_dst.ptr(),
region,
&CLVec::default(),
region[0] * bpp,
region[0] * region[1] * bpp,
&CLVec::default(),
tx_dst.row_pitch() as usize,
tx_dst.slice_pitch() as usize,
bpp as u8,
)
} else {
let bpp = self.image_format.pixel_size().unwrap() as usize;
tx_src = self.tx_image(
q,
ctx,
&create_box(&src_origin, region, self.mem_type)?,
RWFlags::RD,
)?;
tx_dst = dst.tx(q, ctx, dst_origin[0], region.pixels() * bpp, RWFlags::WR)?;
sw_copy(
tx_src.ptr(),
tx_dst.ptr(),
region,
&CLVec::default(),
tx_src.row_pitch() as usize,
tx_src.slice_pitch() as usize,
&CLVec::default(),
region[0] * bpp,
region[0] * region[1] * bpp,
bpp as u8,
)
}
} else {
let bx = create_box(&src_origin, region, self.mem_type)?;
let mut dst_origin: [u32; 3] = dst_origin.try_into()?;
if self.mem_type == CL_MEM_OBJECT_IMAGE1D_ARRAY {
(dst_origin[1], dst_origin[2]) = (dst_origin[2], dst_origin[1]);
}
ctx.resource_copy_region(src_res, dst_res, &dst_origin, &bx);
}
Ok(())
}
pub fn fill(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
pattern: &[u8],
mut offset: usize,
size: usize,
) -> CLResult<()> {
assert!(self.is_buffer());
let b = self.to_parent(&mut offset);
let res = b.get_res()?.get(&q.device).unwrap();
ctx.clear_buffer(
res,
pattern,
offset.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
size.try_into().map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
);
Ok(())
}
pub fn fill_image(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
pattern: &[u32],
origin: &CLVec<usize>,
region: &CLVec<usize>,
) -> CLResult<()> {
assert!(!self.is_buffer());
let res = self.get_res()?.get(&q.device).unwrap();
let bx = create_box(origin, region, self.mem_type)?;
// make sure we allocate multiples of 4 bytes so drivers don't read out of bounds or
// unaligned.
// TODO: use div_ceil once it's available
let size = align(
self.image_format.pixel_size().unwrap() as usize,
size_of::<u32>(),
);
let mut new_pattern: Vec<u32> = vec![0; size / size_of::<u32>()];
// we don't support CL_DEPTH for now
assert!(pattern.len() == 4);
// SAFETY: pointers have to be valid for read/writes of exactly one pixel of their
// respective format.
// `new_pattern` has the correct size due to the `size` above.
// `pattern` is validated through the CL API and allows undefined behavior if not followed
// by CL API rules. It's expected to be a 4 component array of 32 bit values, except for
// CL_DEPTH where it's just one value.
unsafe {
util_format_pack_rgba(
self.image_format.to_pipe_format().unwrap(),
new_pattern.as_mut_ptr().cast(),
pattern.as_ptr().cast(),
1,
);
}
ctx.clear_texture(res, &new_pattern, &bx);
Ok(())
}
pub fn write_from_user_rect(
&self,
src: *const c_void,
q: &Arc<Queue>,
ctx: &PipeContext,
region: &CLVec<usize>,
src_origin: &CLVec<usize>,
src_row_pitch: usize,
mut src_slice_pitch: usize,
dst_origin: &CLVec<usize>,
dst_row_pitch: usize,
dst_slice_pitch: usize,
) -> CLResult<()> {
if self.is_buffer() {
let (offset, size) =
buffer_offset_size(dst_origin, region, dst_row_pitch, dst_slice_pitch);
let tx = self.tx(q, ctx, offset, size, RWFlags::WR)?;
sw_copy(
src,
tx.ptr(),
region,
src_origin,
src_row_pitch,
src_slice_pitch,
&CLVec::default(),
dst_row_pitch,
dst_slice_pitch,
1,
);
} else {
assert!(dst_row_pitch == self.image_desc.image_row_pitch);
assert!(dst_slice_pitch == self.image_desc.image_slice_pitch);
assert!(src_origin == &CLVec::default());
let res = self.get_res()?.get(&q.device).unwrap();
let bx = create_box(dst_origin, region, self.mem_type)?;
if self.mem_type == CL_MEM_OBJECT_IMAGE1D_ARRAY {
src_slice_pitch = src_row_pitch;
}
ctx.texture_subdata(
res,
&bx,
src,
src_row_pitch
.try_into()
.map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
src_slice_pitch
.try_into()
.map_err(|_| CL_OUT_OF_HOST_MEMORY)?,
);
}
Ok(())
}
pub fn read_to_user_rect(
&self,
dst: *mut c_void,
q: &Arc<Queue>,
ctx: &PipeContext,
region: &CLVec<usize>,
src_origin: &CLVec<usize>,
mut src_row_pitch: usize,
mut src_slice_pitch: usize,
dst_origin: &CLVec<usize>,
dst_row_pitch: usize,
dst_slice_pitch: usize,
) -> CLResult<()> {
let tx;
let pixel_size;
if self.is_buffer() {
let (offset, size) =
buffer_offset_size(src_origin, region, src_row_pitch, src_slice_pitch);
tx = self.tx(q, ctx, offset, size, RWFlags::RD)?;
pixel_size = 1;
} else {
assert!(dst_origin == &CLVec::default());
let bx = create_box(src_origin, region, self.mem_type)?;
tx = self.tx_image(q, ctx, &bx, RWFlags::RD)?;
src_row_pitch = tx.row_pitch() as usize;
src_slice_pitch = tx.slice_pitch() as usize;
pixel_size = self.image_format.pixel_size().unwrap();
};
sw_copy(
tx.ptr(),
dst,
region,
&CLVec::default(),
src_row_pitch,
src_slice_pitch,
dst_origin,
dst_row_pitch,
dst_slice_pitch,
pixel_size,
);
Ok(())
}
pub fn copy_to_rect(
&self,
dst: &Self,
q: &Arc<Queue>,
ctx: &PipeContext,
region: &CLVec<usize>,
src_origin: &CLVec<usize>,
src_row_pitch: usize,
src_slice_pitch: usize,
dst_origin: &CLVec<usize>,
dst_row_pitch: usize,
dst_slice_pitch: usize,
) -> CLResult<()> {
assert!(self.is_buffer());
assert!(dst.is_buffer());
let (offset, size) = buffer_offset_size(src_origin, region, src_row_pitch, src_slice_pitch);
let tx_src = self.tx(q, ctx, offset, size, RWFlags::RD)?;
let (offset, size) = buffer_offset_size(dst_origin, region, dst_row_pitch, dst_slice_pitch);
let tx_dst = dst.tx(q, ctx, offset, size, RWFlags::WR)?;
// TODO check to use hw accelerated paths (e.g. resource_copy_region or blits)
sw_copy(
tx_src.ptr(),
tx_dst.ptr(),
region,
&CLVec::default(),
src_row_pitch,
src_slice_pitch,
&CLVec::default(),
dst_row_pitch,
dst_slice_pitch,
1,
);
Ok(())
}
// TODO: only sync on map when the memory is not mapped with discard
pub fn sync_shadow_buffer(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
ptr: *mut c_void,
) -> CLResult<()> {
let mut lock = self.maps.lock().unwrap();
if !lock.increase_ref(&q.device, ptr) {
return Ok(());
}
if self.has_user_shadow_buffer(&q.device)? {
self.read_to_user(q, ctx, 0, self.host_ptr, self.size)
} else {
if let Some(shadow) = lock.tx.get(&q.device).and_then(|tx| tx.shadow.as_ref()) {
let mut offset = 0;
let b = self.to_parent(&mut offset);
let res = b.get_res_of_dev(&q.device)?;
let bx = pipe_box {
width: self.size as i32,
height: 1,
depth: 1,
x: offset as i32,
..Default::default()
};
ctx.resource_copy_region(res, shadow, &[0; 3], &bx);
}
Ok(())
}
}
// TODO: only sync on map when the memory is not mapped with discard
pub fn sync_shadow_image(
&self,
q: &Arc<Queue>,
ctx: &PipeContext,
ptr: *mut c_void,
) -> CLResult<()> {
let mut lock = self.maps.lock().unwrap();
if !lock.increase_ref(&q.device, ptr) {
return Ok(());
}
if self.has_user_shadow_buffer(&q.device)? {
self.read_to_user_rect(
self.host_ptr,
q,
ctx,
&self.image_desc.api_size(),
&CLVec::default(),
0,
0,
&CLVec::default(),
self.image_desc.image_row_pitch,
self.image_desc.image_slice_pitch,
)
} else {
if let Some(shadow) = lock.tx.get(&q.device).and_then(|tx| tx.shadow.as_ref()) {
let res = self.get_res_of_dev(&q.device)?;
let bx = self.image_desc.bx()?;
ctx.resource_copy_region(res, shadow, &[0, 0, 0], &bx);
}
Ok(())
}
}
/// Maps the queue associated device's resource.
///
/// Mapping resources could have been quite straightforward if OpenCL wouldn't allow for so
/// called non blocking maps. Non blocking maps shall return a valid pointer to the mapped
/// region immediately, but should not synchronize data (in case of shadow buffers) until after
/// the map event is reached in the queue.
/// This makes it not possible to simply use pipe_transfers as those can't be explicitly synced
/// by the frontend.
///
/// In order to have a compliant implementation of the mapping API we have to consider the
/// following cases:
/// 1. Mapping a cl_mem object with CL_MEM_USE_HOST_PTR: We simply return the host_ptr.
/// Synchronization of shadowed host ptrs are done in `sync_shadow_buffer` and
/// `sync_shadow_image` on demand.
/// 2. Mapping linear resources on UMA systems: We simply create the pipe_transfer with
/// `PIPE_MAP_DIRECTLY` and `PIPE_MAP_UNSYNCHRONIZED` and return the attached pointer.
/// 3. On non UMA systems or when 2. fails (e.g. due to the resource being tiled) we
/// - create a shadow pipe_resource with `PIPE_USAGE_STAGING`,
/// `PIPE_RESOURCE_FLAG_MAP_PERSISTENT` and `PIPE_RESOURCE_FLAG_MAP_COHERENT`
/// - create a pipe_transfer with `PIPE_MAP_COHERENT`, `PIPE_MAP_PERSISTENT` and
/// `PIPE_MAP_UNSYNCHRONIZED`
/// - sync the shadow buffer like a host_ptr shadow buffer in 1.
///
/// Taking this approach we guarentee that we only copy when actually needed while making sure
/// the content behind the returned pointer is valid until unmapped.
fn map<'a>(
&self,
q: &Arc<Queue>,
lock: &'a mut MutexGuard<Mappings>,
rw: RWFlags,
) -> CLResult<&'a PipeTransfer> {
if !lock.tx.contains_key(&q.device) {
let (tx, res) = if self.is_buffer() {
self.tx_raw_async(q, rw)?
} else {
let bx = self.image_desc.bx()?;
self.tx_image_raw_async(q, &bx, rw)?
};
lock.tx
.insert(q.device.clone(), MappingTransfer::new(tx, res));
} else {
lock.mark_pending(&q.device);
}
Ok(&lock.tx.get_mut(&q.device).unwrap().tx)
}
pub fn map_buffer(&self, q: &Arc<Queue>, offset: usize, _size: usize) -> CLResult<*mut c_void> {
assert!(self.is_buffer());
let mut lock = self.maps.lock().unwrap();
let ptr = if self.has_user_shadow_buffer(&q.device)? {
self.host_ptr
} else {
let tx = self.map(q, &mut lock, RWFlags::RW)?;
tx.ptr()
};
let ptr = unsafe { ptr.add(offset) };
Ok(ptr)
}
pub fn map_image(
&self,
q: &Arc<Queue>,
origin: &CLVec<usize>,
_region: &CLVec<usize>,
row_pitch: &mut usize,
slice_pitch: &mut usize,
) -> CLResult<*mut c_void> {
assert!(!self.is_buffer());
let mut lock = self.maps.lock().unwrap();
// we might have a host_ptr shadow buffer
let ptr = if self.has_user_shadow_buffer(&q.device)? {
*row_pitch = self.image_desc.image_row_pitch;
*slice_pitch = self.image_desc.image_slice_pitch;
self.host_ptr
} else {
let tx = self.map(q, &mut lock, RWFlags::RW)?;
if self.image_desc.dims() > 1 {
*row_pitch = tx.row_pitch() as usize;
}
if self.image_desc.dims() > 2 || self.image_desc.is_array() {
*slice_pitch = tx.slice_pitch() as usize;
}
tx.ptr()
};
let ptr = unsafe {
ptr.add(
*origin
* [
self.image_format.pixel_size().unwrap() as usize,
*row_pitch,
*slice_pitch,
],
)
};
Ok(ptr)
}
pub fn is_mapped_ptr(&self, ptr: *mut c_void) -> bool {
self.maps.lock().unwrap().maps.contains_key(&ptr)
}
// TODO: only sync on unmap when the memory is not mapped for writing
pub fn unmap(&self, q: &Arc<Queue>, ctx: &PipeContext, ptr: *mut c_void) -> CLResult<()> {
let mut lock = self.maps.lock().unwrap();
if !lock.maps.contains_key(&ptr) {
return Ok(());
}
let (needs_sync, shadow) = lock.decrease_ref(ptr, &q.device);
if needs_sync {
if let Some(shadow) = shadow {
let mut offset = 0;
let b = self.to_parent(&mut offset);
let res = b.get_res_of_dev(&q.device)?;
let bx = if b.is_buffer() {
pipe_box {
width: self.size as i32,
height: 1,
depth: 1,
..Default::default()
}
} else {
self.image_desc.bx()?
};
ctx.resource_copy_region(shadow, res, &[offset as u32, 0, 0], &bx);
} else if self.has_user_shadow_buffer(&q.device)? {
if self.is_buffer() {
self.write_from_user(q, ctx, 0, self.host_ptr, self.size)?;
} else {
self.write_from_user_rect(
self.host_ptr,
q,
ctx,
&self.image_desc.api_size(),
&CLVec::default(),
self.image_desc.image_row_pitch,
self.image_desc.image_slice_pitch,
&CLVec::default(),
self.image_desc.image_row_pitch,
self.image_desc.image_slice_pitch,
)?;
}
}
}
lock.clean_up_tx(&q.device, ctx);
Ok(())
}
}
impl Drop for Mem {
fn drop(&mut self) {
let cl = cl_mem::from_ptr(self);
self.cbs
.get_mut()
.unwrap()
.iter()
.rev()
.for_each(|cb| cb(cl));
for (d, tx) in self.maps.lock().unwrap().tx.drain() {
d.helper_ctx().unmap(tx.tx);
}
}
}
#[repr(C)]
pub struct Sampler {
pub base: CLObjectBase<CL_INVALID_SAMPLER>,
pub context: Arc<Context>,
pub normalized_coords: bool,
pub addressing_mode: cl_addressing_mode,
pub filter_mode: cl_filter_mode,
pub props: Option<Properties<cl_sampler_properties>>,
}
impl_cl_type_trait!(cl_sampler, Sampler, CL_INVALID_SAMPLER);
impl Sampler {
pub fn new(
context: Arc<Context>,
normalized_coords: bool,
addressing_mode: cl_addressing_mode,
filter_mode: cl_filter_mode,
props: Option<Properties<cl_sampler_properties>>,
) -> Arc<Sampler> {
Arc::new(Self {
base: CLObjectBase::new(),
context: context,
normalized_coords: normalized_coords,
addressing_mode: addressing_mode,
filter_mode: filter_mode,
props: props,
})
}
pub fn nir_to_cl(
addressing_mode: u32,
filter_mode: u32,
normalized_coords: u32,
) -> (cl_addressing_mode, cl_filter_mode, bool) {
let addr_mode = match addressing_mode {
cl_sampler_addressing_mode::SAMPLER_ADDRESSING_MODE_NONE => CL_ADDRESS_NONE,
cl_sampler_addressing_mode::SAMPLER_ADDRESSING_MODE_CLAMP_TO_EDGE => {
CL_ADDRESS_CLAMP_TO_EDGE
}
cl_sampler_addressing_mode::SAMPLER_ADDRESSING_MODE_CLAMP => CL_ADDRESS_CLAMP,
cl_sampler_addressing_mode::SAMPLER_ADDRESSING_MODE_REPEAT => CL_ADDRESS_REPEAT,
cl_sampler_addressing_mode::SAMPLER_ADDRESSING_MODE_REPEAT_MIRRORED => {
CL_ADDRESS_MIRRORED_REPEAT
}
_ => panic!("unkown addressing_mode"),
};
let filter = match filter_mode {
cl_sampler_filter_mode::SAMPLER_FILTER_MODE_NEAREST => CL_FILTER_NEAREST,
cl_sampler_filter_mode::SAMPLER_FILTER_MODE_LINEAR => CL_FILTER_LINEAR,
_ => panic!("unkown filter_mode"),
};
(addr_mode, filter, normalized_coords != 0)
}
pub fn cl_to_pipe(
(addressing_mode, filter_mode, normalized_coords): (
cl_addressing_mode,
cl_filter_mode,
bool,
),
) -> pipe_sampler_state {
let mut res = pipe_sampler_state::default();
let wrap = match addressing_mode {
CL_ADDRESS_CLAMP_TO_EDGE => pipe_tex_wrap::PIPE_TEX_WRAP_CLAMP_TO_EDGE,
CL_ADDRESS_CLAMP => pipe_tex_wrap::PIPE_TEX_WRAP_CLAMP_TO_BORDER,
CL_ADDRESS_REPEAT => pipe_tex_wrap::PIPE_TEX_WRAP_REPEAT,
CL_ADDRESS_MIRRORED_REPEAT => pipe_tex_wrap::PIPE_TEX_WRAP_MIRROR_REPEAT,
// TODO: what's a reasonable default?
_ => pipe_tex_wrap::PIPE_TEX_WRAP_CLAMP_TO_EDGE,
};
let img_filter = match filter_mode {
CL_FILTER_NEAREST => pipe_tex_filter::PIPE_TEX_FILTER_NEAREST,
CL_FILTER_LINEAR => pipe_tex_filter::PIPE_TEX_FILTER_LINEAR,
_ => panic!("unkown filter_mode"),
};
res.set_min_img_filter(img_filter);
res.set_mag_img_filter(img_filter);
res.set_unnormalized_coords((!normalized_coords).into());
res.set_wrap_r(wrap);
res.set_wrap_s(wrap);
res.set_wrap_t(wrap);
res
}
pub fn pipe(&self) -> pipe_sampler_state {
Self::cl_to_pipe((
self.addressing_mode,
self.filter_mode,
self.normalized_coords,
))
}
}