From c2be6e7f908c75912df25465ba60f60661df02f2 Mon Sep 17 00:00:00 2001 From: khyperia Date: Thu, 26 Nov 2020 13:45:13 +0100 Subject: [PATCH] Implement textures --- crates/rustc_codegen_spirv/src/abi.rs | 53 ++++++ .../src/builder/builder_methods.rs | 4 + .../src/builder/spirv_asm.rs | 61 ++++-- .../src/codegen_cx/constant.rs | 5 + .../src/codegen_cx/type_.rs | 2 + crates/rustc_codegen_spirv/src/spirv_type.rs | 82 +++++++- crates/rustc_codegen_spirv/src/symbols.rs | 176 +++++++++++++++++- crates/spirv-std/src/lib.rs | 3 + crates/spirv-std/src/textures.rs | 52 ++++++ 9 files changed, 416 insertions(+), 22 deletions(-) create mode 100644 crates/spirv-std/src/textures.rs diff --git a/crates/rustc_codegen_spirv/src/abi.rs b/crates/rustc_codegen_spirv/src/abi.rs index 447c231adf..49d34632ab 100644 --- a/crates/rustc_codegen_spirv/src/abi.rs +++ b/crates/rustc_codegen_spirv/src/abi.rs @@ -305,6 +305,13 @@ impl<'tcx> ConvSpirvType<'tcx> for TyAndLayout<'tcx> { } fn trans_type_impl<'tcx>(cx: &CodegenCx<'tcx>, ty: TyAndLayout<'tcx>, is_immediate: bool) -> Word { + if let TyKind::Adt(adt, _) = *ty.ty.kind() { + for attr in parse_attrs(cx, cx.tcx.get_attrs(adt.did)) { + if let Some(image) = trans_image(cx, ty, attr) { + return image; + } + } + } // Note: ty.layout is orthogonal to ty.ty, e.g. `ManuallyDrop>` has abi // `ScalarPair`. // There's a few layers that we go through here. First we inspect layout.abi, then if relevant, layout.fields, etc. @@ -705,3 +712,49 @@ fn name_of_struct(ty: TyAndLayout<'_>) -> String { } name } + +fn trans_image<'tcx>( + cx: &CodegenCx<'tcx>, + ty: TyAndLayout<'tcx>, + attr: SpirvAttribute, +) -> Option { + match attr { + SpirvAttribute::Image { + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + } => { + // see SpirvType::sizeof + if ty.size != Size::from_bytes(4) { + cx.tcx.sess.err("#[spirv(image)] type must have size 4"); + return None; + } + // Hardcode to float for now + let sampled_type = SpirvType::Float(32).def(cx); + let ty = SpirvType::Image { + sampled_type, + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + }; + Some(ty.def(cx)) + } + SpirvAttribute::Sampler => { + // see SpirvType::sizeof + if ty.size != Size::from_bytes(4) { + cx.tcx.sess.err("#[spirv(sampler)] type must have size 4"); + return None; + } + Some(SpirvType::Sampler.def(cx)) + } + _ => None, + } +} diff --git a/crates/rustc_codegen_spirv/src/builder/builder_methods.rs b/crates/rustc_codegen_spirv/src/builder/builder_methods.rs index 7ac3fccc99..6fa76bcb83 100644 --- a/crates/rustc_codegen_spirv/src/builder/builder_methods.rs +++ b/crates/rustc_codegen_spirv/src/builder/builder_methods.rs @@ -185,6 +185,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } SpirvType::Pointer { .. } => self.fatal("memset on pointers not implemented yet"), SpirvType::Function { .. } => self.fatal("memset on functions not implemented yet"), + SpirvType::Image { .. } => self.fatal("cannot memset image"), + SpirvType::Sampler => self.fatal("cannot memset sampler"), } } @@ -238,6 +240,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } SpirvType::Pointer { .. } => self.fatal("memset on pointers not implemented yet"), SpirvType::Function { .. } => self.fatal("memset on functions not implemented yet"), + SpirvType::Image { .. } => self.fatal("cannot memset image"), + SpirvType::Sampler => self.fatal("cannot memset sampler"), } } diff --git a/crates/rustc_codegen_spirv/src/builder/spirv_asm.rs b/crates/rustc_codegen_spirv/src/builder/spirv_asm.rs index 77d82b12d9..e65287cc6e 100644 --- a/crates/rustc_codegen_spirv/src/builder/spirv_asm.rs +++ b/crates/rustc_codegen_spirv/src/builder/spirv_asm.rs @@ -113,13 +113,17 @@ impl<'a, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'tcx> { ); } let line = tokens.last_mut().unwrap(); - if line - .last() - .map_or(false, |prev| matches!(prev, Token::Word("typeof"))) - { - *line.last_mut().unwrap() = Token::Typeof(&operands[operand_idx], span); - } else { - line.push(Token::Placeholder(&operands[operand_idx], span)); + let typeof_kind = line.last().and_then(|prev| match prev { + Token::Word("typeof") => Some(TypeofKind::Plain), + Token::Word("typeof*") => Some(TypeofKind::Dereference), + _ => None, + }); + match typeof_kind { + Some(kind) => { + *line.last_mut().unwrap() = + Token::Typeof(&operands[operand_idx], span, kind) + } + None => line.push(Token::Placeholder(&operands[operand_idx], span)), } } } @@ -132,10 +136,19 @@ impl<'a, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'tcx> { } } +enum TypeofKind { + Plain, + Dereference, +} + enum Token<'a, 'cx, 'tcx> { Word(&'a str), Placeholder(&'a InlineAsmOperandRef<'tcx, Builder<'cx, 'tcx>>, Span), - Typeof(&'a InlineAsmOperandRef<'tcx, Builder<'cx, 'tcx>>, Span), + Typeof( + &'a InlineAsmOperandRef<'tcx, Builder<'cx, 'tcx>>, + Span, + TypeofKind, + ), } enum OutRegister<'a> { @@ -204,7 +217,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { Token::Placeholder(_, _) => true, Token::Word(id_str) if id_str.starts_with('%') => true, Token::Word(_) => false, - Token::Typeof(_, _) => false, + Token::Typeof(_, _, _) => false, } { let result_id = match self.parse_id_out(id_map, first_token) { Some(result_id) => result_id, @@ -230,7 +243,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { }; let inst_name = match first_token { Token::Word(inst_name) => inst_name, - Token::Placeholder(_, span) | Token::Typeof(_, span) => { + Token::Placeholder(_, span) | Token::Typeof(_, span, _) => { self.tcx .sess .span_err(span, "cannot use a dynamic value as an instruction type"); @@ -354,7 +367,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { None } }, - Token::Typeof(_, span) => { + Token::Typeof(_, span, _) => { self.tcx .sess .span_err(span, "cannot assign to a typeof expression"); @@ -432,10 +445,26 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { None } }, - Token::Typeof(hole, span) => match hole { + Token::Typeof(hole, span, kind) => match hole { InlineAsmOperandRef::In { reg, value } => { self.check_reg(span, reg); - Some(value.immediate().ty) + let ty = value.immediate().ty; + Some(match kind { + TypeofKind::Plain => ty, + TypeofKind::Dereference => match self.lookup_type(ty) { + SpirvType::Pointer { pointee, .. } => pointee, + other => { + self.tcx.sess.span_err( + span, + &format!( + "cannot use typeof* on non-pointer type: {}", + other.debug(ty, self) + ), + ); + ty + } + }, + }) } InlineAsmOperandRef::Out { reg, @@ -559,7 +588,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { let word = match token { Token::Word(word) => Some(word), Token::Placeholder(_, _) => None, - Token::Typeof(_, _) => None, + Token::Typeof(_, _, _) => None, }; match (kind, word) { (OperandKind::IdResultType, _) => { @@ -663,7 +692,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { span, &format!("expected a literal, not a dynamic value for a {:?}", kind), ), - Some(Token::Typeof(_, span)) => self.tcx.sess.span_err( + Some(Token::Typeof(_, span, _)) => self.tcx.sess.span_err( span, &format!("expected a literal, not a type for a {:?}", kind), ), @@ -858,7 +887,7 @@ impl<'cx, 'tcx> Builder<'cx, 'tcx> { &format!("expected a literal, not a dynamic value for a {:?}", kind), ); } - Token::Typeof(_, span) => { + Token::Typeof(_, span, _) => { self.tcx.sess.span_err( span, &format!("expected a literal, not a type for a {:?}", kind), diff --git a/crates/rustc_codegen_spirv/src/codegen_cx/constant.rs b/crates/rustc_codegen_spirv/src/codegen_cx/constant.rs index a538266752..89946c4694 100644 --- a/crates/rustc_codegen_spirv/src/codegen_cx/constant.rs +++ b/crates/rustc_codegen_spirv/src/codegen_cx/constant.rs @@ -482,6 +482,11 @@ impl<'tcx> CodegenCx<'tcx> { .tcx .sess .fatal("TODO: SpirvType::Function not supported yet in create_const_alloc"), + SpirvType::Image { .. } => self.tcx.sess.fatal("Cannot create a constant image value"), + SpirvType::Sampler => self + .tcx + .sess + .fatal("Cannot create a constant sampler value"), } } diff --git a/crates/rustc_codegen_spirv/src/codegen_cx/type_.rs b/crates/rustc_codegen_spirv/src/codegen_cx/type_.rs index ba59b55839..0ff62da084 100644 --- a/crates/rustc_codegen_spirv/src/codegen_cx/type_.rs +++ b/crates/rustc_codegen_spirv/src/codegen_cx/type_.rs @@ -174,6 +174,8 @@ impl<'tcx> BaseTypeMethods<'tcx> for CodegenCx<'tcx> { SpirvType::RuntimeArray { .. } => TypeKind::Array, SpirvType::Pointer { .. } => TypeKind::Pointer, SpirvType::Function { .. } => TypeKind::Function, + SpirvType::Image { .. } => TypeKind::Integer, + SpirvType::Sampler => TypeKind::Integer, } } fn type_ptr_to(&self, ty: Self::Type) -> Self::Type { diff --git a/crates/rustc_codegen_spirv/src/spirv_type.rs b/crates/rustc_codegen_spirv/src/spirv_type.rs index e0294d4026..e32a4e140b 100644 --- a/crates/rustc_codegen_spirv/src/spirv_type.rs +++ b/crates/rustc_codegen_spirv/src/spirv_type.rs @@ -3,7 +3,9 @@ use crate::builder_spirv::SpirvValue; use crate::codegen_cx::CodegenCx; use bimap::BiHashMap; use rspirv::dr::Operand; -use rspirv::spirv::{Capability, Decoration, StorageClass, Word}; +use rspirv::spirv::{ + AccessQualifier, Capability, Decoration, Dim, ImageFormat, StorageClass, Word, +}; use rustc_target::abi::{Align, Size}; use std::cell::RefCell; use std::fmt; @@ -56,6 +58,17 @@ pub enum SpirvType { return_type: Word, arguments: Vec, }, + Image { + sampled_type: Word, + dim: Dim, + depth: u32, + arrayed: u32, + multisampled: u32, + sampled: u32, + image_format: ImageFormat, + access_qualifier: Option, + }, + Sampler, } impl SpirvType { @@ -183,6 +196,26 @@ impl SpirvType { } => cx .emit_global() .type_function(return_type, arguments.iter().cloned()), + Self::Image { + sampled_type, + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + } => cx.emit_global().type_image( + sampled_type, + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + ), + Self::Sampler => cx.emit_global().type_sampler(), }; cx.type_cache.def(result, self); result @@ -244,6 +277,8 @@ impl SpirvType { Self::RuntimeArray { .. } => return None, Self::Pointer { .. } => cx.tcx.data_layout.pointer_size, Self::Function { .. } => cx.tcx.data_layout.pointer_size, + Self::Image { .. } => Size::from_bytes(4), + Self::Sampler => Size::from_bytes(4), }; Some(result) } @@ -267,6 +302,8 @@ impl SpirvType { Self::RuntimeArray { element } => cx.lookup_type(element).alignof(cx), Self::Pointer { .. } => cx.tcx.data_layout.pointer_align.abi, Self::Function { .. } => cx.tcx.data_layout.pointer_align.abi, + Self::Image { .. } => Align::from_bytes(4).unwrap(), + Self::Sampler => Align::from_bytes(4).unwrap(), } } } @@ -380,6 +417,28 @@ impl fmt::Debug for SpirvTypePrinter<'_, '_> { .field("arguments", &args) .finish() } + SpirvType::Image { + sampled_type, + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + } => f + .debug_struct("Image") + .field("id", &self.id) + .field("sampled_type", &self.cx.debug_type(sampled_type)) + .field("dim", &dim) + .field("depth", &depth) + .field("arrayed", &arrayed) + .field("multisampled", &multisampled) + .field("sampled", &sampled) + .field("image_format", &image_format) + .field("access_qualifier", &access_qualifier) + .finish(), + SpirvType::Sampler => f.debug_struct("Sampler").field("id", &self.id).finish(), }; { let mut debug_stack = DEBUG_STACK.lock().unwrap(); @@ -489,6 +548,27 @@ impl SpirvTypePrinter<'_, '_> { f.write_str(") -> ")?; ty(self.cx, stack, f, return_type) } + SpirvType::Image { + sampled_type, + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + } => f + .debug_struct("Image") + .field("sampled_type", &self.cx.debug_type(sampled_type)) + .field("dim", &dim) + .field("depth", &depth) + .field("arrayed", &arrayed) + .field("multisampled", &multisampled) + .field("sampled", &sampled) + .field("image_format", &image_format) + .field("access_qualifier", &access_qualifier) + .finish(), + SpirvType::Sampler => f.write_str("Sampler"), } } } diff --git a/crates/rustc_codegen_spirv/src/symbols.rs b/crates/rustc_codegen_spirv/src/symbols.rs index 7b81336052..45dd86c529 100644 --- a/crates/rustc_codegen_spirv/src/symbols.rs +++ b/crates/rustc_codegen_spirv/src/symbols.rs @@ -1,6 +1,8 @@ use crate::builder::libm_intrinsics; use crate::codegen_cx::CodegenCx; -use rspirv::spirv::{BuiltIn, ExecutionMode, ExecutionModel, StorageClass}; +use rspirv::spirv::{ + AccessQualifier, BuiltIn, Dim, ExecutionMode, ExecutionModel, ImageFormat, StorageClass, +}; use rustc_ast::ast::{AttrKind, Attribute, Lit, LitIntType, LitKind, NestedMetaItem}; use rustc_span::symbol::{Ident, Symbol}; use std::collections::HashMap; @@ -30,7 +32,14 @@ pub struct Symbols { pub spirv15: Symbol, descriptor_set: Symbol, binding: Symbol, - really_unsafe_ignore_bitcasts: Symbol, + image: Symbol, + dim: Symbol, + depth: Symbol, + arrayed: Symbol, + multisampled: Symbol, + sampled: Symbol, + image_format: Symbol, + access_qualifier: Symbol, attributes: HashMap, execution_modes: HashMap, pub libm_intrinsics: HashMap, @@ -321,9 +330,19 @@ impl Symbols { let execution_models = EXECUTION_MODELS .iter() .map(|&(a, b)| (a, SpirvAttribute::Entry(b.into()))); + let custom_attributes = [ + ( + "really_unsafe_ignore_bitcasts", + SpirvAttribute::ReallyUnsafeIgnoreBitcasts, + ), + ("sampler", SpirvAttribute::Sampler), + ] + .iter() + .cloned(); let attributes_iter = builtins .chain(storage_classes) .chain(execution_models) + .chain(custom_attributes) .map(|(a, b)| (Symbol::intern(a), b)); let mut attributes = HashMap::new(); for (a, b) in attributes_iter { @@ -362,7 +381,14 @@ impl Symbols { spirv15: Symbol::intern("spirv1.5"), descriptor_set: Symbol::intern("descriptor_set"), binding: Symbol::intern("binding"), - really_unsafe_ignore_bitcasts: Symbol::intern("really_unsafe_ignore_bitcasts"), + image: Symbol::intern("image"), + dim: Symbol::intern("dim"), + depth: Symbol::intern("depth"), + arrayed: Symbol::intern("arrayed"), + multisampled: Symbol::intern("multisampled"), + sampled: Symbol::intern("sampled"), + image_format: Symbol::intern("image_format"), + access_qualifier: Symbol::intern("access_qualifier"), attributes, execution_modes, libm_intrinsics, @@ -415,6 +441,16 @@ pub enum SpirvAttribute { DescriptorSet(u32), Binding(u32), ReallyUnsafeIgnoreBitcasts, + Image { + dim: Dim, + depth: u32, + arrayed: u32, + multisampled: u32, + sampled: u32, + image_format: ImageFormat, + access_qualifier: Option, + }, + Sampler, } // Note that we could mark the attr as used via cx.tcx.sess.mark_attr_used(attr), but unused @@ -448,8 +484,8 @@ pub fn parse_attrs( Vec::new() }; args.into_iter().filter_map(move |ref arg| { - if arg.has_name(cx.sym.really_unsafe_ignore_bitcasts) { - Some(SpirvAttribute::ReallyUnsafeIgnoreBitcasts) + if arg.has_name(cx.sym.image) { + parse_image(cx, arg) } else if arg.has_name(cx.sym.descriptor_set) { match parse_attr_int_value(cx, arg) { Some(x) => Some(SpirvAttribute::DescriptorSet(x)), @@ -493,6 +529,136 @@ pub fn parse_attrs( result.collect::>().into_iter() } +fn parse_image(cx: &CodegenCx<'_>, attr: &NestedMetaItem) -> Option { + let args = match attr.meta_item_list() { + Some(args) => args, + None => { + cx.tcx + .sess + .span_err(attr.span(), "image attribute must have arguments"); + return None; + } + }; + if args.len() != 6 && args.len() != 7 { + cx.tcx + .sess + .span_err(attr.span(), "image attribute must have 6 or 7 arguments"); + return None; + } + let check = |idx: usize, sym: Symbol| -> bool { + if args[idx].has_name(sym) { + false + } else { + cx.tcx.sess.span_err( + args[idx].span(), + &format!("image attribute argument {} must be {}=...", idx + 1, sym), + ); + true + } + }; + if check(0, cx.sym.dim) + | check(1, cx.sym.depth) + | check(2, cx.sym.arrayed) + | check(3, cx.sym.multisampled) + | check(4, cx.sym.sampled) + | check(5, cx.sym.image_format) + | (args.len() == 7 && check(6, cx.sym.access_qualifier)) + { + return None; + } + let arg_values = args + .iter() + .map( + |arg| match arg.meta_item().and_then(|arg| arg.name_value_literal()) { + Some(arg) => Some(arg), + None => { + cx.tcx + .sess + .span_err(arg.span(), "image attribute must be name=value"); + None + } + }, + ) + .collect::>>()?; + let dim = match arg_values[0].kind { + LitKind::Str(dim, _) => match dim.with(|s| s.parse()) { + Ok(dim) => dim, + Err(()) => { + cx.tcx.sess.span_err(args[0].span(), "invalid dim value"); + return None; + } + }, + _ => { + cx.tcx + .sess + .span_err(args[0].span(), "dim value must be str"); + return None; + } + }; + let parse_lit = |idx: usize, name: &str| -> Option { + match arg_values[idx].kind { + LitKind::Int(v, _) => Some(v as u32), + _ => { + cx.tcx + .sess + .span_err(args[idx].span(), &format!("{} value must be int", name)); + None + } + } + }; + let depth = parse_lit(1, "depth")?; + let arrayed = parse_lit(2, "arrayed")?; + let multisampled = parse_lit(3, "multisampled")?; + let sampled = parse_lit(4, "sampled")?; + let image_format = match arg_values[5].kind { + LitKind::Str(dim, _) => match dim.with(|s| s.parse()) { + Ok(dim) => dim, + Err(()) => { + cx.tcx + .sess + .span_err(args[5].span(), "invalid image_format value"); + return None; + } + }, + _ => { + cx.tcx + .sess + .span_err(args[5].span(), "image_format value must be str"); + return None; + } + }; + let access_qualifier = if args.len() == 7 { + Some(match arg_values[6].kind { + LitKind::Str(dim, _) => match dim.with(|s| s.parse()) { + Ok(dim) => dim, + Err(()) => { + cx.tcx + .sess + .span_err(args[6].span(), "invalid access_qualifier value"); + return None; + } + }, + _ => { + cx.tcx + .sess + .span_err(args[6].span(), "access_qualifier value must be str"); + return None; + } + }) + } else { + None + }; + Some(SpirvAttribute::Image { + dim, + depth, + arrayed, + multisampled, + sampled, + image_format, + access_qualifier, + }) +} + fn parse_attr_int_value(cx: &CodegenCx<'_>, arg: &NestedMetaItem) -> Option { let arg = match arg.meta_item() { Some(arg) => arg, diff --git a/crates/spirv-std/src/lib.rs b/crates/spirv-std/src/lib.rs index 7c9c6b7858..a485a9b6fb 100644 --- a/crates/spirv-std/src/lib.rs +++ b/crates/spirv-std/src/lib.rs @@ -37,8 +37,11 @@ nonstandard_style )] +mod textures; + pub use glam; pub use num_traits; +pub use textures::*; macro_rules! pointer_addrspace_write { (false) => {}; diff --git a/crates/spirv-std/src/textures.rs b/crates/spirv-std/src/textures.rs new file mode 100644 index 0000000000..3b74453cce --- /dev/null +++ b/crates/spirv-std/src/textures.rs @@ -0,0 +1,52 @@ +use glam::{Vec2, Vec4}; + +#[allow(unused_attributes)] +#[spirv(sampler)] +#[derive(Copy, Clone)] +pub struct Sampler { + _x: u32, +} + +#[allow(unused_attributes)] +#[spirv(image( + // sampled_type is hardcoded to f32 for now + dim = "Dim2D", + depth = 0, + arrayed = 0, + multisampled = 0, + sampled = 1, + image_format = "Unknown" +))] +#[derive(Copy, Clone)] +pub struct Image2d { + _x: u32, +} + +impl Image2d { + pub fn sample(&self, sampler: Sampler, coord: Vec2) -> Vec4 { + #[cfg(not(target_arch = "spirv"))] + { + let _ = sampler; + let _ = coord; + panic!("Image sampling not supported on CPU"); + } + #[cfg(target_arch = "spirv")] + unsafe { + let mut result = Default::default(); + asm!( + "%typeSampledImage = OpTypeSampledImage typeof*{1}", + "%image = OpLoad typeof*{1} {1}", + "%sampler = OpLoad typeof*{2} {2}", + "%coord = OpLoad typeof*{3} {3}", + "%sampledImage = OpSampledImage %typeSampledImage %image %sampler", + "%result = OpImageSampleImplicitLod typeof*{0} %sampledImage %coord", + "OpStore {0} %result", + in(reg) &mut result, + in(reg) self, + in(reg) &sampler, + in(reg) &coord + ); + result + } + } +}