rt(d3d12): move update subresources to lut
This commit is contained in:
parent
7d483f2e08
commit
dedde05c83
|
@ -3,7 +3,7 @@ use crate::error;
|
||||||
use crate::filter_chain::FrameResiduals;
|
use crate::filter_chain::FrameResiduals;
|
||||||
use crate::mipmap::MipmapGenContext;
|
use crate::mipmap::MipmapGenContext;
|
||||||
use crate::texture::InputTexture;
|
use crate::texture::InputTexture;
|
||||||
use crate::util::{d3d12_get_closest_format, d3d12_resource_transition, d3d12_update_subresources};
|
use crate::util::{d3d12_get_closest_format, d3d12_resource_transition};
|
||||||
use d3d12_descriptor_heap::D3D12DescriptorHeap;
|
use d3d12_descriptor_heap::D3D12DescriptorHeap;
|
||||||
use gpu_allocator::d3d12::{
|
use gpu_allocator::d3d12::{
|
||||||
Allocator, Resource, ResourceCategory, ResourceCreateDesc, ResourceStateOrBarrierLayout,
|
Allocator, Resource, ResourceCategory, ResourceCreateDesc, ResourceStateOrBarrierLayout,
|
||||||
|
@ -16,18 +16,10 @@ use librashader_runtime::scaling::MipmapSize;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use windows::Win32::Graphics::Direct3D12::{
|
use windows::Win32::Graphics::Direct3D12::{ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, D3D12_FEATURE_DATA_FORMAT_SUPPORT, D3D12_FORMAT_SUPPORT1_MIP, D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE, D3D12_FORMAT_SUPPORT1_TEXTURE2D, D3D12_MEMCPY_DEST, D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_DESC, D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_DIMENSION_TEXTURE2D, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_SHADER_RESOURCE_VIEW_DESC, D3D12_SHADER_RESOURCE_VIEW_DESC_0, D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA, D3D12_TEX2D_SRV, D3D12_TEXTURE_COPY_LOCATION, D3D12_TEXTURE_COPY_LOCATION_0, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, D3D12_TEXTURE_LAYOUT_ROW_MAJOR};
|
||||||
ID3D12Device, ID3D12GraphicsCommandList, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
|
||||||
D3D12_FEATURE_DATA_FORMAT_SUPPORT, D3D12_FORMAT_SUPPORT1_MIP,
|
|
||||||
D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE, D3D12_FORMAT_SUPPORT1_TEXTURE2D,
|
|
||||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT, D3D12_RESOURCE_DESC, D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
||||||
D3D12_RESOURCE_DIMENSION_TEXTURE2D, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
|
|
||||||
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
||||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_SHADER_RESOURCE_VIEW_DESC,
|
|
||||||
D3D12_SHADER_RESOURCE_VIEW_DESC_0, D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_SUBRESOURCE_DATA,
|
|
||||||
D3D12_TEX2D_SRV, D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
|
||||||
};
|
|
||||||
use windows::Win32::Graphics::Dxgi::Common::DXGI_SAMPLE_DESC;
|
use windows::Win32::Graphics::Dxgi::Common::DXGI_SAMPLE_DESC;
|
||||||
|
use std::u64;
|
||||||
|
use crate::error::assume_d3d12_init;
|
||||||
|
|
||||||
pub struct LutTexture {
|
pub struct LutTexture {
|
||||||
resource: ManuallyDrop<Resource>,
|
resource: ManuallyDrop<Resource>,
|
||||||
|
@ -238,3 +230,158 @@ impl Drop for LutTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn d3d12_update_subresources(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
destination_resource: &ID3D12Resource,
|
||||||
|
intermediate_resource: &ID3D12Resource,
|
||||||
|
intermediate_offset: u64,
|
||||||
|
first_subresouce: u32,
|
||||||
|
num_subresources: u32,
|
||||||
|
source: &[D3D12_SUBRESOURCE_DATA],
|
||||||
|
gc: &mut FrameResiduals,
|
||||||
|
) -> error::Result<u64> {
|
||||||
|
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
|
||||||
|
// + std::mem::size_of::<u32>()
|
||||||
|
// + std::mem::size_of::<u64>() * num_subresources;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let destination_desc = destination_resource.GetDesc();
|
||||||
|
let mut device: Option<ID3D12Device> = None;
|
||||||
|
destination_resource.GetDevice(&mut device)?;
|
||||||
|
assume_d3d12_init!(device, "GetDevice");
|
||||||
|
|
||||||
|
let mut layouts =
|
||||||
|
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
|
||||||
|
let mut num_rows = vec![0; num_subresources as usize];
|
||||||
|
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
|
||||||
|
let mut required_size = 0;
|
||||||
|
|
||||||
|
// texture upload
|
||||||
|
device.GetCopyableFootprints(
|
||||||
|
&destination_desc,
|
||||||
|
first_subresouce,
|
||||||
|
num_subresources,
|
||||||
|
intermediate_offset,
|
||||||
|
Some(layouts.as_mut_ptr()),
|
||||||
|
Some(num_rows.as_mut_ptr()),
|
||||||
|
Some(row_sizes_in_bytes.as_mut_ptr()),
|
||||||
|
Some(&mut required_size),
|
||||||
|
);
|
||||||
|
|
||||||
|
update_subresources(
|
||||||
|
cmd,
|
||||||
|
destination_resource,
|
||||||
|
intermediate_resource,
|
||||||
|
first_subresouce,
|
||||||
|
num_subresources,
|
||||||
|
required_size,
|
||||||
|
&layouts,
|
||||||
|
&num_rows,
|
||||||
|
&row_sizes_in_bytes,
|
||||||
|
source,
|
||||||
|
gc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_subresources(
|
||||||
|
cmd: &ID3D12GraphicsCommandList,
|
||||||
|
destination_resource: &ID3D12Resource,
|
||||||
|
intermediate_resource: &ID3D12Resource,
|
||||||
|
first_subresouce: u32,
|
||||||
|
num_subresources: u32,
|
||||||
|
required_size: u64,
|
||||||
|
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
|
||||||
|
num_rows: &[u32],
|
||||||
|
row_sizes_in_bytes: &[u64],
|
||||||
|
source_data: &[D3D12_SUBRESOURCE_DATA],
|
||||||
|
gc: &mut FrameResiduals,
|
||||||
|
) -> error::Result<u64> {
|
||||||
|
// ToDo: implement validation as in the original function
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut data = std::ptr::null_mut();
|
||||||
|
intermediate_resource.Map(0, None, Some(&mut data))?;
|
||||||
|
|
||||||
|
for i in 0..num_subresources as usize {
|
||||||
|
let dest_data = D3D12_MEMCPY_DEST {
|
||||||
|
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
|
||||||
|
RowPitch: layouts[i].Footprint.RowPitch as usize,
|
||||||
|
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
memcpy_subresource(
|
||||||
|
&dest_data,
|
||||||
|
&source_data[i],
|
||||||
|
row_sizes_in_bytes[i],
|
||||||
|
num_rows[i],
|
||||||
|
layouts[i].Footprint.Depth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
intermediate_resource.Unmap(0, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let destination_desc = destination_resource.GetDesc();
|
||||||
|
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
|
||||||
|
cmd.CopyBufferRegion(
|
||||||
|
destination_resource,
|
||||||
|
0,
|
||||||
|
intermediate_resource,
|
||||||
|
layouts[0].Offset,
|
||||||
|
layouts[0].Footprint.Width as u64,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
for i in 0..num_subresources as usize {
|
||||||
|
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||||
|
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
|
||||||
|
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
||||||
|
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||||
|
SubresourceIndex: i as u32 + first_subresouce,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_location = D3D12_TEXTURE_COPY_LOCATION {
|
||||||
|
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
|
||||||
|
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
||||||
|
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
||||||
|
PlacedFootprint: layouts[i],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
|
||||||
|
|
||||||
|
gc.dispose_resource(dest_location.pResource);
|
||||||
|
gc.dispose_resource(source_location.pResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(required_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function should not leak to the public API, so
|
||||||
|
// there is no point in using struct wrappers
|
||||||
|
unsafe fn memcpy_subresource(
|
||||||
|
dest: &D3D12_MEMCPY_DEST,
|
||||||
|
src: &D3D12_SUBRESOURCE_DATA,
|
||||||
|
row_sizes_in_bytes: u64,
|
||||||
|
num_rows: u32,
|
||||||
|
num_slices: u32,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
for z in 0..num_slices as usize {
|
||||||
|
let dest_slice = dest.pData.add(dest.SlicePitch * z);
|
||||||
|
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
|
||||||
|
|
||||||
|
for y in 0..num_rows as usize {
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
src_slice.offset(src.RowPitch * y as isize),
|
||||||
|
dest_slice.add(dest.RowPitch * y),
|
||||||
|
row_sizes_in_bytes as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::error::assume_d3d12_init;
|
|
||||||
|
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::u64;
|
|
||||||
use widestring::{u16cstr, U16CStr};
|
use widestring::{u16cstr, U16CStr};
|
||||||
use windows::core::{Interface, PCWSTR};
|
use windows::core::{Interface, PCWSTR};
|
||||||
use windows::Win32::Graphics::Direct3D::Dxc::{
|
use windows::Win32::Graphics::Direct3D::Dxc::{
|
||||||
|
@ -10,15 +7,14 @@ use windows::Win32::Graphics::Direct3D::Dxc::{
|
||||||
DXC_CP_UTF8,
|
DXC_CP_UTF8,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::filter_chain::FrameResiduals;
|
|
||||||
use windows::Win32::Graphics::Direct3D12::{
|
use windows::Win32::Graphics::Direct3D12::{
|
||||||
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_FEATURE_DATA_FORMAT_SUPPORT,
|
ID3D12Device, ID3D12GraphicsCommandList, ID3D12Resource, D3D12_FEATURE_DATA_FORMAT_SUPPORT,
|
||||||
D3D12_FEATURE_FORMAT_SUPPORT, D3D12_MEMCPY_DEST, D3D12_PLACED_SUBRESOURCE_FOOTPRINT,
|
D3D12_FEATURE_FORMAT_SUPPORT,
|
||||||
D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
D3D12_RESOURCE_BARRIER, D3D12_RESOURCE_BARRIER_0, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
||||||
D3D12_RESOURCE_BARRIER_FLAG_NONE, D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
D3D12_RESOURCE_BARRIER_FLAG_NONE, D3D12_RESOURCE_BARRIER_TYPE_TRANSITION
|
||||||
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_STATES, D3D12_RESOURCE_TRANSITION_BARRIER,
|
, D3D12_RESOURCE_STATES, D3D12_RESOURCE_TRANSITION_BARRIER
|
||||||
D3D12_SUBRESOURCE_DATA, D3D12_TEXTURE_COPY_LOCATION, D3D12_TEXTURE_COPY_LOCATION_0,
|
|
||||||
D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
,
|
||||||
};
|
};
|
||||||
use windows::Win32::Graphics::Dxgi::Common::*;
|
use windows::Win32::Graphics::Dxgi::Common::*;
|
||||||
|
|
||||||
|
@ -237,157 +233,3 @@ pub fn d3d12_resource_transition_subresource(
|
||||||
barrier
|
barrier
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn d3d12_update_subresources(
|
|
||||||
cmd: &ID3D12GraphicsCommandList,
|
|
||||||
destination_resource: &ID3D12Resource,
|
|
||||||
intermediate_resource: &ID3D12Resource,
|
|
||||||
intermediate_offset: u64,
|
|
||||||
first_subresouce: u32,
|
|
||||||
num_subresources: u32,
|
|
||||||
source: &[D3D12_SUBRESOURCE_DATA],
|
|
||||||
gc: &mut FrameResiduals,
|
|
||||||
) -> error::Result<u64> {
|
|
||||||
// let allocation_size = std::mem::size_of::<D3D12_PLACED_SUBRESOURCE_FOOTPRINT>()
|
|
||||||
// + std::mem::size_of::<u32>()
|
|
||||||
// + std::mem::size_of::<u64>() * num_subresources;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let destination_desc = destination_resource.GetDesc();
|
|
||||||
let mut device: Option<ID3D12Device> = None;
|
|
||||||
destination_resource.GetDevice(&mut device)?;
|
|
||||||
assume_d3d12_init!(device, "GetDevice");
|
|
||||||
|
|
||||||
let mut layouts =
|
|
||||||
vec![D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default(); num_subresources as usize];
|
|
||||||
let mut num_rows = vec![0; num_subresources as usize];
|
|
||||||
let mut row_sizes_in_bytes = vec![0; num_subresources as usize];
|
|
||||||
let mut required_size = 0;
|
|
||||||
|
|
||||||
// texture upload
|
|
||||||
device.GetCopyableFootprints(
|
|
||||||
&destination_desc,
|
|
||||||
first_subresouce,
|
|
||||||
num_subresources,
|
|
||||||
intermediate_offset,
|
|
||||||
Some(layouts.as_mut_ptr()),
|
|
||||||
Some(num_rows.as_mut_ptr()),
|
|
||||||
Some(row_sizes_in_bytes.as_mut_ptr()),
|
|
||||||
Some(&mut required_size),
|
|
||||||
);
|
|
||||||
|
|
||||||
update_subresources(
|
|
||||||
cmd,
|
|
||||||
destination_resource,
|
|
||||||
intermediate_resource,
|
|
||||||
first_subresouce,
|
|
||||||
num_subresources,
|
|
||||||
required_size,
|
|
||||||
&layouts,
|
|
||||||
&num_rows,
|
|
||||||
&row_sizes_in_bytes,
|
|
||||||
source,
|
|
||||||
gc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn update_subresources(
|
|
||||||
cmd: &ID3D12GraphicsCommandList,
|
|
||||||
destination_resource: &ID3D12Resource,
|
|
||||||
intermediate_resource: &ID3D12Resource,
|
|
||||||
first_subresouce: u32,
|
|
||||||
num_subresources: u32,
|
|
||||||
required_size: u64,
|
|
||||||
layouts: &[D3D12_PLACED_SUBRESOURCE_FOOTPRINT],
|
|
||||||
num_rows: &[u32],
|
|
||||||
row_sizes_in_bytes: &[u64],
|
|
||||||
source_data: &[D3D12_SUBRESOURCE_DATA],
|
|
||||||
gc: &mut FrameResiduals,
|
|
||||||
) -> error::Result<u64> {
|
|
||||||
// ToDo: implement validation as in the original function
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut data = std::ptr::null_mut();
|
|
||||||
intermediate_resource.Map(0, None, Some(&mut data))?;
|
|
||||||
|
|
||||||
for i in 0..num_subresources as usize {
|
|
||||||
let dest_data = D3D12_MEMCPY_DEST {
|
|
||||||
pData: data.offset(layouts[i].Offset as isize) as *mut std::ffi::c_void,
|
|
||||||
RowPitch: layouts[i].Footprint.RowPitch as usize,
|
|
||||||
SlicePitch: ((layouts[i].Footprint.RowPitch) * num_rows[i]) as usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
memcpy_subresource(
|
|
||||||
&dest_data,
|
|
||||||
&source_data[i],
|
|
||||||
row_sizes_in_bytes[i],
|
|
||||||
num_rows[i],
|
|
||||||
layouts[i].Footprint.Depth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
intermediate_resource.Unmap(0, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let destination_desc = destination_resource.GetDesc();
|
|
||||||
if destination_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
|
|
||||||
cmd.CopyBufferRegion(
|
|
||||||
destination_resource,
|
|
||||||
0,
|
|
||||||
intermediate_resource,
|
|
||||||
layouts[0].Offset,
|
|
||||||
layouts[0].Footprint.Width as u64,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
for i in 0..num_subresources as usize {
|
|
||||||
let dest_location = D3D12_TEXTURE_COPY_LOCATION {
|
|
||||||
pResource: ManuallyDrop::new(Some(destination_resource.clone())),
|
|
||||||
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
||||||
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
|
||||||
SubresourceIndex: i as u32 + first_subresouce,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let source_location = D3D12_TEXTURE_COPY_LOCATION {
|
|
||||||
pResource: ManuallyDrop::new(Some(intermediate_resource.clone())),
|
|
||||||
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
|
||||||
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
|
|
||||||
PlacedFootprint: layouts[i],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
cmd.CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, None);
|
|
||||||
|
|
||||||
gc.dispose_resource(dest_location.pResource);
|
|
||||||
gc.dispose_resource(source_location.pResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(required_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function should not leak to the public API, so
|
|
||||||
// there is no point in using struct wrappers
|
|
||||||
unsafe fn memcpy_subresource(
|
|
||||||
dest: &D3D12_MEMCPY_DEST,
|
|
||||||
src: &D3D12_SUBRESOURCE_DATA,
|
|
||||||
row_sizes_in_bytes: u64,
|
|
||||||
num_rows: u32,
|
|
||||||
num_slices: u32,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
for z in 0..num_slices as usize {
|
|
||||||
let dest_slice = dest.pData.add(dest.SlicePitch * z);
|
|
||||||
let src_slice = src.pData.offset(src.SlicePitch * z as isize);
|
|
||||||
|
|
||||||
for y in 0..num_rows as usize {
|
|
||||||
std::ptr::copy_nonoverlapping(
|
|
||||||
src_slice.offset(src.RowPitch * y as isize),
|
|
||||||
dest_slice.add(dest.RowPitch * y),
|
|
||||||
row_sizes_in_bytes as usize,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue