rusticl/device: refactor Device::new

Adding a DeviceBase type will allow us to do pass in a proper spirv_caps
object into SPIRVBin::get_lib_clc. Should also make device creation more
flexible.

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/35439>
This commit is contained in:
Karol Herbst 2025-06-10 20:29:36 +02:00 committed by Marge Bot
parent 95eb537548
commit 3b2af3006e

View file

@ -29,13 +29,14 @@ use std::ffi::CStr;
use std::fmt::Debug;
use std::mem::transmute;
use std::num::NonZeroU64;
use std::ops::Deref;
use std::os::raw::*;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::MutexGuard;
pub struct Device {
pub base: CLObjectBase<CL_INVALID_DEVICE>,
/// Contains basic stuff we need to partially initialize Device
pub struct DeviceBase {
pub screen: Arc<PipeScreen>,
pub cl_version: CLVersion,
pub clc_version: CLVersion,
@ -47,12 +48,25 @@ pub struct Device {
pub spirv_extensions: Vec<&'static CStr>,
pub clc_features: Vec<cl_name_version>,
pub formats: HashMap<cl_image_format, HashMap<cl_mem_object_type, cl_mem_flags>>,
pub lib_clc: NirShader,
pub caps: DeviceCaps,
helper_ctx: Mutex<PipeContext>,
reusable_ctx: Mutex<Vec<PipeContext>>,
}
pub struct Device {
pub base: CLObjectBase<CL_INVALID_DEVICE>,
dev_base: DeviceBase,
pub lib_clc: NirShader,
}
impl Deref for Device {
type Target = DeviceBase;
fn deref(&self) -> &Self::Target {
&self.dev_base
}
}
#[derive(Default)]
pub struct DeviceCaps {
pub has_3d_image_writes: bool,
@ -236,62 +250,7 @@ impl HelperContextWrapper for HelperContext<'_> {
impl_cl_type_trait_base!(cl_device_id, Device, [Device], CL_INVALID_DEVICE);
impl Device {
fn new(screen: PipeScreen) -> Option<Device> {
if !Self::check_valid(&screen) {
return None;
}
let screen = Arc::new(screen);
// Create before loading libclc as llvmpipe only creates the shader cache with the first
// context being created.
let helper_ctx = screen.create_context()?;
let lib_clc = spirv::SPIRVBin::get_lib_clc(&screen);
if lib_clc.is_none() {
eprintln!("Libclc failed to load. Please make sure it is installed and provides spirv-mesa3d-.spv and/or spirv64-mesa3d-.spv");
}
let mut d = Self {
caps: DeviceCaps::new(&screen),
base: CLObjectBase::new(RusticlTypes::Device),
helper_ctx: Mutex::new(helper_ctx),
screen: screen,
cl_version: CLVersion::Cl3_0,
clc_version: CLVersion::Cl3_0,
clc_versions: Vec::new(),
device_type: 0,
embedded: false,
extension_string: String::from(""),
extensions: Vec::new(),
spirv_extensions: Vec::new(),
clc_features: Vec::new(),
formats: HashMap::new(),
lib_clc: lib_clc?,
reusable_ctx: Mutex::new(Vec::new()),
};
// check if we are embedded or full profile first
d.embedded = d.check_embedded_profile();
d.set_device_type();
d.fill_format_tables();
// query supported extensions
d.fill_extensions();
// now figure out what version we are
d.check_version();
Some(d)
}
/// Converts a temporary reference to a static if and only if this device lives inside static
/// memory.
pub fn to_static(&self) -> Option<&'static Self> {
devs().iter().find(|&dev| self == dev)
}
impl DeviceBase {
fn fill_format_tables(&mut self) {
// no need to do this if we don't support images
if !self.caps.has_images {
@ -424,29 +383,6 @@ impl Device {
}
}
fn check_valid(screen: &PipeScreen) -> bool {
if !screen.caps().compute
|| screen
.shader_caps(pipe_shader_type::PIPE_SHADER_COMPUTE)
.supported_irs
& (1 << (pipe_shader_ir::PIPE_SHADER_IR_NIR as i32))
== 0
{
return false;
}
// CL_DEVICE_MAX_PARAMETER_SIZE
// For this minimum value, only a maximum of 128 arguments can be passed to a kernel
if screen
.shader_caps(pipe_shader_type::PIPE_SHADER_COMPUTE)
.max_const_buffer0_size
< 128
{
return false;
}
true
}
fn check_custom(&self) -> bool {
// Max size of memory object allocation in bytes. The minimum value is
// max(min(1024 × 1024 × 1024, 1/4th of CL_DEVICE_GLOBAL_MEM_SIZE), 32 × 1024 × 1024)
@ -777,42 +713,6 @@ impl Device {
.shader_caps(pipe_shader_type::PIPE_SHADER_COMPUTE)
}
pub fn all() -> Vec<Device> {
let mut devs: Vec<_> = load_screens().filter_map(Device::new).collect();
// Pick a default device. One must be the default one no matter what. And custom devices can
// only be that one if they are the only devices available.
//
// The entry with the highest value will be the default device.
let default = devs.iter_mut().max_by_key(|dev| {
let mut val = if dev.device_type == CL_DEVICE_TYPE_CUSTOM {
// needs to be small enough so it's always going to be the smallest value
-100
} else if dev.device_type == CL_DEVICE_TYPE_CPU {
0
} else if dev.unified_memory() {
// we give unified memory devices max priority, because we don't want to spin up the
// discrete GPU on laptops by default.
100
} else {
10
};
// we deprioritize zink for now.
if dev.screen.driver_name() == c"zink" {
val -= 1;
}
val
});
if let Some(default) = default {
default.device_type |= CL_DEVICE_TYPE_DEFAULT;
}
devs
}
pub fn address_bits(&self) -> cl_uint {
self.screen.compute_caps().address_bits
}
@ -1258,6 +1158,124 @@ impl Device {
}
}
impl Device {
fn new(screen: PipeScreen) -> Option<Device> {
if !Self::check_valid(&screen) {
return None;
}
let screen = Arc::new(screen);
// Create before loading libclc as llvmpipe only creates the shader cache with the first
// context being created.
let helper_ctx = screen.create_context()?;
let lib_clc = spirv::SPIRVBin::get_lib_clc(&screen);
if lib_clc.is_none() {
eprintln!("Libclc failed to load. Please make sure it is installed and provides spirv-mesa3d-.spv and/or spirv64-mesa3d-.spv");
}
let mut dev_base = DeviceBase {
caps: DeviceCaps::new(&screen),
helper_ctx: Mutex::new(helper_ctx),
screen: screen,
cl_version: CLVersion::Cl3_0,
clc_version: CLVersion::Cl3_0,
clc_versions: Vec::new(),
device_type: 0,
embedded: false,
extension_string: String::from(""),
extensions: Vec::new(),
spirv_extensions: Vec::new(),
clc_features: Vec::new(),
formats: HashMap::new(),
reusable_ctx: Mutex::new(Vec::new()),
};
// check if we are embedded or full profile first
dev_base.embedded = dev_base.check_embedded_profile();
dev_base.set_device_type();
dev_base.fill_format_tables();
// query supported extensions
dev_base.fill_extensions();
// now figure out what version we are
dev_base.check_version();
Some(Device {
base: CLObjectBase::new(RusticlTypes::Device),
dev_base: dev_base,
lib_clc: lib_clc?,
})
}
pub fn all() -> Vec<Device> {
let mut devs: Vec<_> = load_screens().filter_map(Device::new).collect();
// Pick a default device. One must be the default one no matter what. And custom devices can
// only be that one if they are the only devices available.
//
// The entry with the highest value will be the default device.
let default = devs.iter_mut().max_by_key(|dev| {
let mut val = if dev.device_type == CL_DEVICE_TYPE_CUSTOM {
// needs to be small enough so it's always going to be the smallest value
-100
} else if dev.device_type == CL_DEVICE_TYPE_CPU {
0
} else if dev.unified_memory() {
// we give unified memory devices max priority, because we don't want to spin up the
// discrete GPU on laptops by default.
100
} else {
10
};
// we deprioritize zink for now.
if dev.screen.driver_name() == c"zink" {
val -= 1;
}
val
});
if let Some(default) = default {
default.dev_base.device_type |= CL_DEVICE_TYPE_DEFAULT;
}
devs
}
fn check_valid(screen: &PipeScreen) -> bool {
if !screen.caps().compute
|| screen
.shader_caps(pipe_shader_type::PIPE_SHADER_COMPUTE)
.supported_irs
& (1 << (pipe_shader_ir::PIPE_SHADER_IR_NIR as i32))
== 0
{
return false;
}
// CL_DEVICE_MAX_PARAMETER_SIZE
// For this minimum value, only a maximum of 128 arguments can be passed to a kernel
if screen
.shader_caps(pipe_shader_type::PIPE_SHADER_COMPUTE)
.max_const_buffer0_size
< 128
{
return false;
}
true
}
/// Converts a temporary reference to a static if and only if this device lives inside static
/// memory.
pub fn to_static(&self) -> Option<&'static Self> {
devs().iter().find(|&dev| self == dev)
}
}
impl Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("Device@{:?}", self as *const _))