mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-02-19 18:33:30 +00:00
Rework shader and pipeline creation
This commit is contained in:
parent
569cd0cdd6
commit
0ea4cac04b
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1890,6 +1890,8 @@ name = "wgpu-hal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"naga",
|
||||
"smallvec",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
|
@ -7,7 +7,7 @@ members = [
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
default-members = ["wgpu"]
|
||||
default-members = ["wgpu", "player"]
|
||||
|
||||
[patch."https://github.com/gfx-rs/naga"]
|
||||
#naga = { path = "../naga" }
|
||||
|
@ -3,10 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{
|
||||
device::{
|
||||
descriptor::{DescriptorSet, DescriptorTotalCount},
|
||||
DeviceError, MissingFeatures, SHADER_STAGE_COUNT,
|
||||
},
|
||||
device::{descriptor::DescriptorSet, DeviceError, MissingFeatures, SHADER_STAGE_COUNT},
|
||||
hub::Resource,
|
||||
id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureViewId, Valid},
|
||||
memory_init_tracker::MemoryInitTrackerAction,
|
||||
@ -390,11 +387,10 @@ pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BindGroupLayout<A: hal::Api> {
|
||||
pub(crate) raw: B::DescriptorSetLayout,
|
||||
pub(crate) raw: A::BindGroupLayout,
|
||||
pub(crate) device_id: Stored<DeviceId>,
|
||||
pub(crate) multi_ref_count: MultiRefCount,
|
||||
pub(crate) entries: BindEntryMap,
|
||||
pub(crate) desc_count: DescriptorTotalCount,
|
||||
pub(crate) dynamic_count: usize,
|
||||
pub(crate) count_validator: BindingTypeMaxCountValidator,
|
||||
#[cfg(debug_assertions)]
|
||||
@ -499,7 +495,7 @@ pub struct PipelineLayoutDescriptor<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipelineLayout<A: hal::Api> {
|
||||
pub(crate) raw: B::PipelineLayout,
|
||||
pub(crate) raw: A::PipelineLayout,
|
||||
pub(crate) device_id: Stored<DeviceId>,
|
||||
pub(crate) life_guard: LifeGuard,
|
||||
pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; hal::MAX_BIND_GROUPS]>,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -555,7 +555,7 @@ pub struct Hub<A: hal::Api, F: GlobalIdentityHandlerFactory> {
|
||||
pub samplers: Registry<Sampler<A>, SamplerId, F>,
|
||||
}
|
||||
|
||||
impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<B, F> {
|
||||
impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<A, F> {
|
||||
fn new(factory: &F) -> Self {
|
||||
Self {
|
||||
adapters: Registry::new(A::VARIANT, factory),
|
||||
@ -578,7 +578,7 @@ impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<B, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<B, F> {
|
||||
impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<A, F> {
|
||||
//TODO: instead of having a hacky `with_adapters` parameter,
|
||||
// we should have `clear_device(device_id)` that specifically destroys
|
||||
// everything related to a logical device.
|
||||
@ -696,7 +696,7 @@ impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<B, F> {
|
||||
//TODO: hold the surface alive by the swapchain
|
||||
if surface_guard.contains(suf_id) {
|
||||
let surface = surface_guard.get_mut(suf_id).unwrap();
|
||||
let suf = B::get_surface_mut(surface);
|
||||
let suf = A::get_surface_mut(surface);
|
||||
unsafe {
|
||||
suf.unconfigure_swapchain(&device.raw);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{
|
||||
conv,
|
||||
device::{Device, DeviceDescriptor},
|
||||
hub::{Global, GlobalIdentityHandlerFactory, HalApi, Input, Token},
|
||||
id::{AdapterId, DeviceId, SurfaceId, Valid},
|
||||
@ -85,21 +84,21 @@ impl Instance {
|
||||
}
|
||||
}
|
||||
|
||||
type GfxSurface<A> = <B as hal::Api>::Surface;
|
||||
type HalSurface<A> = <A as hal::Api>::Surface;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Surface {
|
||||
/*
|
||||
#[cfg(vulkan)]
|
||||
pub vulkan: Option<GfxSurface<backend::Vulkan>>,
|
||||
pub vulkan: Option<HalSurface<backend::Vulkan>>,
|
||||
#[cfg(metal)]
|
||||
pub metal: Option<GfxSurface<backend::Metal>>,
|
||||
pub metal: Option<HalSurface<backend::Metal>>,
|
||||
#[cfg(dx12)]
|
||||
pub dx12: Option<GfxSurface<backend::Dx12>>,
|
||||
pub dx12: Option<HalSurface<backend::Dx12>>,
|
||||
#[cfg(dx11)]
|
||||
pub dx11: Option<GfxSurface<backend::Dx11>>,
|
||||
pub dx11: Option<HalSurface<backend::Dx11>>,
|
||||
#[cfg(gl)]
|
||||
pub gl: Option<GfxSurface<backend::Gl>>,
|
||||
pub gl: Option<HalSurface<backend::Gl>>,
|
||||
*/}
|
||||
|
||||
impl crate::hub::Resource for Surface {
|
||||
@ -142,7 +141,7 @@ impl<A: HalApi> Adapter<A> {
|
||||
wgt::TextureFormat::Rgba8Unorm,
|
||||
];
|
||||
|
||||
let formats = B::get_surface(surface).supported_formats(&self.raw.adapter);
|
||||
let formats = A::get_surface(surface).supported_formats(&self.raw.adapter);
|
||||
preferred_formats
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -487,35 +486,35 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
/*
|
||||
#[cfg(vulkan)]
|
||||
let adapters_vk = map((&instance.vulkan, &id_vulkan, {
|
||||
fn surface_vulkan(surf: &Surface) -> Option<&GfxSurface<backend::Vulkan>> {
|
||||
fn surface_vulkan(surf: &Surface) -> Option<&HalSurface<backend::Vulkan>> {
|
||||
surf.vulkan.as_ref()
|
||||
}
|
||||
surface_vulkan
|
||||
}));
|
||||
#[cfg(metal)]
|
||||
let adapters_mtl = map((&instance.metal, &id_metal, {
|
||||
fn surface_metal(surf: &Surface) -> Option<&GfxSurface<backend::Metal>> {
|
||||
fn surface_metal(surf: &Surface) -> Option<&HalSurface<backend::Metal>> {
|
||||
surf.metal.as_ref()
|
||||
}
|
||||
surface_metal
|
||||
}));
|
||||
#[cfg(dx12)]
|
||||
let adapters_dx12 = map((&instance.dx12, &id_dx12, {
|
||||
fn surface_dx12(surf: &Surface) -> Option<&GfxSurface<backend::Dx12>> {
|
||||
fn surface_dx12(surf: &Surface) -> Option<&HalSurface<backend::Dx12>> {
|
||||
surf.dx12.as_ref()
|
||||
}
|
||||
surface_dx12
|
||||
}));
|
||||
#[cfg(dx11)]
|
||||
let adapters_dx11 = map((&instance.dx11, &id_dx11, {
|
||||
fn surface_dx11(surf: &Surface) -> Option<&GfxSurface<backend::Dx11>> {
|
||||
fn surface_dx11(surf: &Surface) -> Option<&HalSurface<backend::Dx11>> {
|
||||
surf.dx11.as_ref()
|
||||
}
|
||||
surface_dx11
|
||||
}));
|
||||
#[cfg(gl)]
|
||||
let adapters_gl = map((&instance.gl, &id_gl, {
|
||||
fn surface_gl(surf: &Surface) -> Option<&GfxSurface<backend::Gl>> {
|
||||
fn surface_gl(surf: &Surface) -> Option<&HalSurface<backend::Gl>> {
|
||||
surf.gl.as_ref()
|
||||
}
|
||||
surface_gl
|
||||
@ -604,7 +603,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
let (adapter_guard, _) = hub.adapters.read(&mut token);
|
||||
adapter_guard
|
||||
.get(adapter_id)
|
||||
.map(|adapter| conv::map_adapter_info(adapter.raw.info.clone(), adapter_id.backend()))
|
||||
.map(|adapter| adapter.raw.info.clone())
|
||||
.map_err(|_| InvalidAdapter)
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,10 @@ use crate::{
|
||||
use std::borrow::Cow;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderModuleSource<'a> {
|
||||
SpirV(Cow<'a, [u32]>),
|
||||
Wgsl(Cow<'a, str>),
|
||||
Naga(naga::Module),
|
||||
Naga(&'a naga::Module),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -30,7 +29,7 @@ pub struct ShaderModuleDescriptor<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShaderModule<A: hal::Api> {
|
||||
pub(crate) raw: B::ShaderModule,
|
||||
pub(crate) raw: A::ShaderModule,
|
||||
pub(crate) device_id: Stored<DeviceId>,
|
||||
pub(crate) interface: Option<validation::Interface>,
|
||||
#[cfg(debug_assertions)]
|
||||
@ -126,7 +125,7 @@ pub enum CreateComputePipelineError {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ComputePipeline<A: hal::Api> {
|
||||
pub(crate) raw: B::ComputePipeline,
|
||||
pub(crate) raw: A::ComputePipeline,
|
||||
pub(crate) layout_id: Stored<PipelineLayoutId>,
|
||||
pub(crate) device_id: Stored<DeviceId>,
|
||||
pub(crate) life_guard: LifeGuard,
|
||||
@ -290,7 +289,7 @@ bitflags::bitflags! {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderPipeline<A: hal::Api> {
|
||||
pub(crate) raw: B::GraphicsPipeline,
|
||||
pub(crate) raw: A::RenderPipeline,
|
||||
pub(crate) layout_id: Stored<PipelineLayoutId>,
|
||||
pub(crate) device_id: Stored<DeviceId>,
|
||||
pub(crate) pass_context: RenderPassContext,
|
||||
|
@ -16,55 +16,8 @@ use thiserror::Error;
|
||||
|
||||
use std::{borrow::Borrow, num::NonZeroU8, ops::Range, ptr::NonNull};
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// The internal enum mirrored from `BufferUsage`. The values don't have to match!
|
||||
pub struct BufferUse: u32 {
|
||||
const EMPTY = 0;
|
||||
const MAP_READ = 1;
|
||||
const MAP_WRITE = 2;
|
||||
const COPY_SRC = 4;
|
||||
const COPY_DST = 8;
|
||||
const INDEX = 16;
|
||||
const VERTEX = 32;
|
||||
const UNIFORM = 64;
|
||||
const STORAGE_LOAD = 128;
|
||||
const STORAGE_STORE = 256;
|
||||
const INDIRECT = 512;
|
||||
/// The combination of all read-only usages.
|
||||
const READ_ALL = Self::MAP_READ.bits | Self::COPY_SRC.bits |
|
||||
Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits |
|
||||
Self::STORAGE_LOAD.bits | Self::INDIRECT.bits;
|
||||
/// The combination of all write-only and read-write usages.
|
||||
const WRITE_ALL = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE_STORE.bits;
|
||||
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
|
||||
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
|
||||
/// still need to be pipeline barriers inserted for synchronization.
|
||||
const ORDERED = Self::READ_ALL.bits | Self::MAP_WRITE.bits | Self::COPY_DST.bits;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// The internal enum mirrored from `TextureUsage`. The values don't have to match!
|
||||
pub struct TextureUse: u32 {
|
||||
const EMPTY = 0;
|
||||
const COPY_SRC = 1;
|
||||
const COPY_DST = 2;
|
||||
const SAMPLED = 4;
|
||||
const ATTACHMENT_READ = 8;
|
||||
const ATTACHMENT_WRITE = 16;
|
||||
const STORAGE_LOAD = 32;
|
||||
const STORAGE_STORE = 48;
|
||||
/// The combination of all read-only usages.
|
||||
const READ_ALL = Self::COPY_SRC.bits | Self::SAMPLED.bits | Self::ATTACHMENT_READ.bits | Self::STORAGE_LOAD.bits;
|
||||
/// The combination of all write-only and read-write usages.
|
||||
const WRITE_ALL = Self::COPY_DST.bits | Self::ATTACHMENT_WRITE.bits | Self::STORAGE_STORE.bits;
|
||||
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
|
||||
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
|
||||
/// still need to be pipeline barriers inserted for synchronization.
|
||||
const ORDERED = Self::READ_ALL.bits | Self::COPY_DST.bits | Self::ATTACHMENT_WRITE.bits;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
}
|
||||
}
|
||||
//TODO: remove the alias, just use it from `hal`
|
||||
pub(crate) use hal::{BufferUse, TextureUse};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
|
@ -7,7 +7,7 @@ mod range;
|
||||
mod texture;
|
||||
|
||||
use crate::{
|
||||
conv, hub,
|
||||
hub,
|
||||
id::{self, TypedId, Valid},
|
||||
resource, Epoch, FastHashMap, Index, RefCount,
|
||||
};
|
||||
@ -124,24 +124,6 @@ pub(crate) struct PendingTransition<S: ResourceState> {
|
||||
pub usage: ops::Range<S::Usage>,
|
||||
}
|
||||
|
||||
impl PendingTransition<BufferState> {
|
||||
/// Produce the gfx-hal barrier corresponding to the transition.
|
||||
pub fn into_hal<'a, A: hal::Api>(
|
||||
self,
|
||||
buf: &'a resource::Buffer<A>,
|
||||
) -> hal::memory::Barrier<'a, B> {
|
||||
log::trace!("\tbuffer -> {:?}", self);
|
||||
let &(ref target, _) = buf.raw.as_ref().expect("Buffer is destroyed");
|
||||
hal::memory::Barrier::Buffer {
|
||||
states: conv::map_buffer_state(self.usage.start)
|
||||
..conv::map_buffer_state(self.usage.end),
|
||||
target,
|
||||
range: hal::buffer::SubRange::WHOLE,
|
||||
families: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PendingTransition<BufferState>> for UsageConflict {
|
||||
fn from(e: PendingTransition<BufferState>) -> Self {
|
||||
Self::Buffer {
|
||||
@ -151,31 +133,6 @@ impl From<PendingTransition<BufferState>> for UsageConflict {
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingTransition<TextureState> {
|
||||
/// Produce the gfx-hal barrier corresponding to the transition.
|
||||
pub fn into_hal<'a, A: hal::Api>(
|
||||
self,
|
||||
tex: &'a resource::Texture<A>,
|
||||
) -> hal::memory::Barrier<'a, B> {
|
||||
log::trace!("\ttexture -> {:?}", self);
|
||||
let &(ref target, _) = tex.raw.as_ref().expect("Texture is destroyed");
|
||||
let aspects = tex.aspects;
|
||||
hal::memory::Barrier::Image {
|
||||
states: conv::map_texture_state(self.usage.start, aspects)
|
||||
..conv::map_texture_state(self.usage.end, aspects),
|
||||
target,
|
||||
range: hal::image::SubresourceRange {
|
||||
aspects,
|
||||
level_start: self.selector.levels.start,
|
||||
level_count: Some(self.selector.levels.end - self.selector.levels.start),
|
||||
layer_start: self.selector.layers.start,
|
||||
layer_count: Some(self.selector.layers.end - self.selector.layers.start),
|
||||
},
|
||||
families: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PendingTransition<TextureState>> for UsageConflict {
|
||||
fn from(e: PendingTransition<TextureState>) -> Self {
|
||||
Self::Texture {
|
||||
|
@ -15,4 +15,9 @@ license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
smallvec = "1"
|
||||
wgt = { package = "wgpu-types", path = "../wgpu-types" }
|
||||
|
||||
[dependencies.naga]
|
||||
git = "https://github.com/gfx-rs/naga"
|
||||
tag = "gfx-25"
|
||||
|
@ -1,39 +1,68 @@
|
||||
#[derive(Clone)]
|
||||
pub struct Api;
|
||||
pub struct Surface;
|
||||
pub struct Adapter;
|
||||
pub struct Queue;
|
||||
pub struct Device;
|
||||
pub struct CommandBuffer;
|
||||
pub struct RenderPass;
|
||||
pub struct ComputePass;
|
||||
pub struct Context;
|
||||
pub struct Encoder;
|
||||
#[derive(Debug)]
|
||||
pub struct Resource;
|
||||
|
||||
impl crate::Surface<Api> for Surface {}
|
||||
impl crate::Api for Api {
|
||||
type Instance = Context;
|
||||
type Surface = Context;
|
||||
type Adapter = Context;
|
||||
type Queue = Context;
|
||||
type Device = Context;
|
||||
|
||||
impl crate::Adapter<Api> for Adapter {
|
||||
type CommandBuffer = Encoder;
|
||||
type RenderPass = Encoder;
|
||||
type ComputePass = Encoder;
|
||||
|
||||
type Buffer = Resource;
|
||||
type QuerySet = Resource;
|
||||
type Texture = Resource;
|
||||
type SwapChainTexture = Resource;
|
||||
type TextureView = Resource;
|
||||
type Sampler = Resource;
|
||||
|
||||
type BindGroupLayout = Resource;
|
||||
type BindGroup = Resource;
|
||||
type PipelineLayout = Resource;
|
||||
type ShaderModule = Resource;
|
||||
type RenderPipeline = Resource;
|
||||
type ComputePipeline = Resource;
|
||||
}
|
||||
|
||||
impl crate::Instance<Api> for Context {
|
||||
unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<Api>> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Surface<Api> for Context {}
|
||||
|
||||
impl crate::Adapter<Api> for Context {
|
||||
unsafe fn open(
|
||||
&self,
|
||||
_features: wgt::Features,
|
||||
) -> Result<crate::OpenDevice<Api>, crate::Error> {
|
||||
Err(crate::Error::DeviceLost)
|
||||
}
|
||||
unsafe fn close(&self, _device: Device) {}
|
||||
unsafe fn close(&self, _device: Context) {}
|
||||
unsafe fn texture_format_capabilities(
|
||||
&self,
|
||||
_format: wgt::TextureFormat,
|
||||
) -> crate::TextureFormatCapability {
|
||||
crate::TextureFormatCapability::empty()
|
||||
}
|
||||
unsafe fn surface_formats(&self, _surface: &Surface) -> Vec<wgt::TextureFormat> {
|
||||
unsafe fn surface_formats(&self, _surface: &Context) -> Vec<wgt::TextureFormat> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Queue<Api> for Queue {
|
||||
unsafe fn submit<I: Iterator<Item = CommandBuffer>>(&mut self, _command_buffers: I) {}
|
||||
impl crate::Queue<Api> for Context {
|
||||
unsafe fn submit<I: Iterator<Item = Encoder>>(&mut self, _command_buffers: I) {}
|
||||
}
|
||||
|
||||
impl crate::Device<Api> for Device {
|
||||
impl crate::Device<Api> for Context {
|
||||
unsafe fn create_buffer(
|
||||
&self,
|
||||
_desc: &wgt::BufferDescriptor<crate::Label>,
|
||||
@ -79,52 +108,76 @@ impl crate::Device<Api> for Device {
|
||||
unsafe fn destroy_texture_view(&self, _view: Resource) {}
|
||||
unsafe fn create_sampler(
|
||||
&self,
|
||||
desc: &crate::SamplerDescriptor<crate::Label>,
|
||||
_desc: &crate::SamplerDescriptor,
|
||||
) -> Result<Resource, crate::Error> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_sampler(&self, _sampler: Resource) {}
|
||||
|
||||
unsafe fn create_command_buffer(&self) -> Result<CommandBuffer, crate::Error> {
|
||||
Ok(CommandBuffer)
|
||||
unsafe fn create_command_buffer(&self) -> Result<Encoder, crate::Error> {
|
||||
Ok(Encoder)
|
||||
}
|
||||
unsafe fn destroy_command_buffer(&self, _cmd_buf: Encoder) {}
|
||||
|
||||
unsafe fn create_bind_group_layout(
|
||||
&self,
|
||||
_desc: &crate::BindGroupLayoutDescriptor,
|
||||
) -> Result<Resource, crate::Error> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_bind_group_layout(&self, _bg_layout: Resource) {}
|
||||
unsafe fn create_pipeline_layout(
|
||||
&self,
|
||||
_desc: &crate::PipelineLayoutDescriptor<Api>,
|
||||
) -> Result<Resource, crate::Error> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: Resource) {}
|
||||
unsafe fn create_bind_group(
|
||||
&self,
|
||||
_desc: &crate::BindGroupDescriptor<Api>,
|
||||
) -> Result<Resource, crate::Error> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_bind_group(&self, _group: Resource) {}
|
||||
|
||||
unsafe fn create_shader_module(
|
||||
&self,
|
||||
_desc: &crate::ShaderModuleDescriptor,
|
||||
_shader: crate::NagaShader,
|
||||
) -> Result<Resource, (crate::ShaderError, crate::NagaShader)> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_shader_module(&self, _module: Resource) {}
|
||||
unsafe fn create_render_pipeline(
|
||||
&self,
|
||||
_desc: &crate::RenderPipelineDescriptor<Api>,
|
||||
) -> Result<Resource, crate::PipelineError> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_render_pipeline(&self, _pipeline: Resource) {}
|
||||
unsafe fn create_compute_pipeline(
|
||||
&self,
|
||||
_desc: &crate::ComputePipelineDescriptor<Api>,
|
||||
) -> Result<Resource, crate::PipelineError> {
|
||||
Ok(Resource)
|
||||
}
|
||||
unsafe fn destroy_compute_pipeline(&self, _pipeline: Resource) {}
|
||||
}
|
||||
|
||||
impl crate::CommandBuffer<Api> for CommandBuffer {
|
||||
impl crate::CommandBuffer<Api> for Encoder {
|
||||
unsafe fn begin(&mut self) {}
|
||||
unsafe fn end(&mut self) {}
|
||||
|
||||
unsafe fn begin_render_pass(&mut self) -> RenderPass {
|
||||
RenderPass
|
||||
unsafe fn begin_render_pass(&mut self) -> Encoder {
|
||||
Encoder
|
||||
}
|
||||
unsafe fn end_render_pass(&mut self, _pass: RenderPass) {}
|
||||
unsafe fn begin_compute_pass(&mut self) -> ComputePass {
|
||||
ComputePass
|
||||
unsafe fn end_render_pass(&mut self, _pass: Encoder) {}
|
||||
unsafe fn begin_compute_pass(&mut self) -> Encoder {
|
||||
Encoder
|
||||
}
|
||||
unsafe fn end_compute_pass(&mut self, _pass: ComputePass) {}
|
||||
unsafe fn end_compute_pass(&mut self, _pass: Encoder) {}
|
||||
}
|
||||
|
||||
impl crate::RenderPass<Api> for RenderPass {}
|
||||
impl crate::ComputePass<Api> for ComputePass {}
|
||||
|
||||
impl crate::Api for Api {
|
||||
type Surface = Surface;
|
||||
type Adapter = Adapter;
|
||||
type Queue = Queue;
|
||||
type Device = Device;
|
||||
|
||||
type CommandBuffer = CommandBuffer;
|
||||
type RenderPass = RenderPass;
|
||||
type ComputePass = ComputePass;
|
||||
|
||||
type Buffer = Resource;
|
||||
type QuerySet = Resource;
|
||||
type Texture = Resource;
|
||||
type SwapChainTexture = Resource;
|
||||
type TextureView = Resource;
|
||||
type Sampler = Resource;
|
||||
|
||||
unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<Api>> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
impl crate::RenderPass<Api> for Encoder {}
|
||||
impl crate::ComputePass<Api> for Encoder {}
|
||||
|
@ -5,11 +5,33 @@
|
||||
/*! This library describes the internal unsafe graphics abstraction API.
|
||||
*/
|
||||
|
||||
#![allow(
|
||||
// We use loops for getting early-out of scope without closures.
|
||||
clippy::never_loop,
|
||||
// We don't use syntax sugar where it's not necessary.
|
||||
clippy::match_like_matches_macro,
|
||||
// Redundant matching is more explicit.
|
||||
clippy::redundant_pattern_matching,
|
||||
// Explicit lifetimes are often easier to reason about.
|
||||
clippy::needless_lifetimes,
|
||||
// No need for defaults in the internal types.
|
||||
clippy::new_without_default,
|
||||
)]
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates,
|
||||
unused_qualifications,
|
||||
// We don't match on a reference, unless required.
|
||||
clippy::pattern_type_mismatch,
|
||||
)]
|
||||
|
||||
pub mod empty;
|
||||
|
||||
use std::{fmt, num::NonZeroU8, ops::Range, ptr::NonNull};
|
||||
use std::{borrow::Cow, fmt, num::NonZeroU8, ops::Range, ptr::NonNull};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub const MAX_ANISOTROPY: u8 = 16;
|
||||
pub const MAX_BIND_GROUPS: usize = 8;
|
||||
@ -35,7 +57,56 @@ impl fmt::Display for Error {
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub trait Api: Sized {
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderError {
|
||||
Compilation(String),
|
||||
Device(Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for ShaderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Compilation(ref message) => write!(f, "compilation failed: {}", message),
|
||||
Self::Device(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ShaderError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
Self::Compilation(..) => None,
|
||||
Self::Device(ref parent) => Some(parent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PipelineError {
|
||||
Linkage(wgt::ShaderStage, String),
|
||||
Device(Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for PipelineError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Linkage(stage, ref message) => {
|
||||
write!(f, "linkage failed for stage {:?}: {}", stage, message)
|
||||
}
|
||||
Self::Device(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for PipelineError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
Self::Linkage(..) => None,
|
||||
Self::Device(ref parent) => Some(parent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Api: Clone + Sized {
|
||||
type Instance: Instance<Self>;
|
||||
type Surface: Surface<Self>;
|
||||
type Adapter: Adapter<Self>;
|
||||
type Device: Device<Self>;
|
||||
@ -45,14 +116,23 @@ pub trait Api: Sized {
|
||||
type RenderPass: RenderPass<Self>;
|
||||
type ComputePass: ComputePass<Self>;
|
||||
|
||||
type Buffer;
|
||||
type QuerySet;
|
||||
type Texture;
|
||||
type SwapChainTexture;
|
||||
type TextureView;
|
||||
type Sampler;
|
||||
type Buffer: fmt::Debug + Send + Sync;
|
||||
type QuerySet: fmt::Debug + Send + Sync;
|
||||
type Texture: fmt::Debug + Send + Sync;
|
||||
type SwapChainTexture: fmt::Debug + Send + Sync;
|
||||
type TextureView: fmt::Debug + Send + Sync;
|
||||
type Sampler: fmt::Debug + Send + Sync;
|
||||
|
||||
unsafe fn enumerate_adapters(&self) -> Vec<ExposedAdapter<Self>>;
|
||||
type BindGroupLayout;
|
||||
type BindGroup: fmt::Debug + Send + Sync;
|
||||
type PipelineLayout;
|
||||
type ShaderModule: fmt::Debug + Send + Sync;
|
||||
type RenderPipeline;
|
||||
type ComputePipeline;
|
||||
}
|
||||
|
||||
pub trait Instance<A: Api> {
|
||||
unsafe fn enumerate_adapters(&self) -> Vec<ExposedAdapter<A>>;
|
||||
}
|
||||
|
||||
pub trait Surface<A: Api> {}
|
||||
@ -102,10 +182,44 @@ pub trait Device<A: Api> {
|
||||
desc: &TextureViewDescriptor<Label>,
|
||||
) -> Result<A::TextureView, Error>;
|
||||
unsafe fn destroy_texture_view(&self, view: A::TextureView);
|
||||
unsafe fn create_sampler(&self, desc: &SamplerDescriptor<Label>) -> Result<A::Sampler, Error>;
|
||||
unsafe fn create_sampler(&self, desc: &SamplerDescriptor) -> Result<A::Sampler, Error>;
|
||||
unsafe fn destroy_sampler(&self, sampler: A::Sampler);
|
||||
|
||||
unsafe fn create_command_buffer(&self) -> Result<A::CommandBuffer, Error>;
|
||||
unsafe fn destroy_command_buffer(&self, cmd_buf: A::CommandBuffer);
|
||||
|
||||
unsafe fn create_bind_group_layout(
|
||||
&self,
|
||||
desc: &BindGroupLayoutDescriptor,
|
||||
) -> Result<A::BindGroupLayout, Error>;
|
||||
unsafe fn destroy_bind_group_layout(&self, bg_layout: A::BindGroupLayout);
|
||||
unsafe fn create_pipeline_layout(
|
||||
&self,
|
||||
desc: &PipelineLayoutDescriptor<A>,
|
||||
) -> Result<A::PipelineLayout, Error>;
|
||||
unsafe fn destroy_pipeline_layout(&self, pipeline_layout: A::PipelineLayout);
|
||||
unsafe fn create_bind_group(
|
||||
&self,
|
||||
desc: &BindGroupDescriptor<A>,
|
||||
) -> Result<A::BindGroup, Error>;
|
||||
unsafe fn destroy_bind_group(&self, group: A::BindGroup);
|
||||
|
||||
unsafe fn create_shader_module(
|
||||
&self,
|
||||
desc: &ShaderModuleDescriptor,
|
||||
shader: NagaShader,
|
||||
) -> Result<A::ShaderModule, (ShaderError, NagaShader)>;
|
||||
unsafe fn destroy_shader_module(&self, module: A::ShaderModule);
|
||||
unsafe fn create_render_pipeline(
|
||||
&self,
|
||||
desc: &RenderPipelineDescriptor<A>,
|
||||
) -> Result<A::RenderPipeline, PipelineError>;
|
||||
unsafe fn destroy_render_pipeline(&self, pipeline: A::RenderPipeline);
|
||||
unsafe fn create_compute_pipeline(
|
||||
&self,
|
||||
desc: &ComputePipelineDescriptor<A>,
|
||||
) -> Result<A::ComputePipeline, PipelineError>;
|
||||
unsafe fn destroy_compute_pipeline(&self, pipeline: A::ComputePipeline);
|
||||
}
|
||||
|
||||
pub trait Queue<A: Api> {
|
||||
@ -125,38 +239,6 @@ pub trait CommandBuffer<A: Api> {
|
||||
pub trait RenderPass<A: Api> {}
|
||||
pub trait ComputePass<A: Api> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Alignments {
|
||||
/// The alignment of the start of the buffer used as a GPU copy source.
|
||||
pub buffer_copy_offset: wgt::BufferSize,
|
||||
/// The alignment of the row pitch of the texture data stored in a buffer that is
|
||||
/// used in a GPU copy operation.
|
||||
pub buffer_copy_pitch: wgt::BufferSize,
|
||||
pub storage_buffer_offset: wgt::BufferSize,
|
||||
pub uniform_buffer_offset: wgt::BufferSize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Capabilities {
|
||||
pub limits: wgt::Limits,
|
||||
pub alignments: Alignments,
|
||||
pub downlevel: wgt::DownlevelCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExposedAdapter<A: Api> {
|
||||
pub adapter: A::Adapter,
|
||||
pub info: wgt::AdapterInfo,
|
||||
pub features: wgt::Features,
|
||||
pub capabilities: Capabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OpenDevice<A: Api> {
|
||||
pub device: A::Device,
|
||||
pub queue: A::Queue,
|
||||
}
|
||||
|
||||
bitflags!(
|
||||
/// Texture format capability flags.
|
||||
pub struct TextureFormatCapability: u32 {
|
||||
@ -207,6 +289,87 @@ impl From<wgt::TextureFormat> for FormatAspect {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Similar to `wgt::BufferUsage` but for internal use.
|
||||
pub struct BufferUse: u32 {
|
||||
const MAP_READ = 1;
|
||||
const MAP_WRITE = 2;
|
||||
const COPY_SRC = 4;
|
||||
const COPY_DST = 8;
|
||||
const INDEX = 16;
|
||||
const VERTEX = 32;
|
||||
const UNIFORM = 64;
|
||||
const STORAGE_LOAD = 128;
|
||||
const STORAGE_STORE = 256;
|
||||
const INDIRECT = 512;
|
||||
/// The combination of all read-only usages.
|
||||
const READ_ALL = Self::MAP_READ.bits | Self::COPY_SRC.bits |
|
||||
Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits |
|
||||
Self::STORAGE_LOAD.bits | Self::INDIRECT.bits;
|
||||
/// The combination of all write-only and read-write usages.
|
||||
const WRITE_ALL = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE_STORE.bits;
|
||||
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
|
||||
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
|
||||
/// still need to be pipeline barriers inserted for synchronization.
|
||||
const ORDERED = Self::READ_ALL.bits | Self::MAP_WRITE.bits | Self::COPY_DST.bits;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Similar to `wgt::TextureUsage` but for internal use.
|
||||
pub struct TextureUse: u32 {
|
||||
const COPY_SRC = 1;
|
||||
const COPY_DST = 2;
|
||||
const SAMPLED = 4;
|
||||
const COLOR_TARGET = 8;
|
||||
const DEPTH_STENCIL_READ = 16;
|
||||
const DEPTH_STENCIL_WRITE = 32;
|
||||
const STORAGE_LOAD = 64;
|
||||
const STORAGE_STORE = 128;
|
||||
/// The combination of all read-only usages.
|
||||
const READ_ALL = Self::COPY_SRC.bits | Self::SAMPLED.bits | Self::DEPTH_STENCIL_READ.bits | Self::STORAGE_LOAD.bits;
|
||||
/// The combination of all write-only and read-write usages.
|
||||
const WRITE_ALL = Self::COPY_DST.bits | Self::COLOR_TARGET.bits | Self::DEPTH_STENCIL_WRITE.bits | Self::STORAGE_STORE.bits;
|
||||
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
|
||||
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
|
||||
/// still need to be pipeline barriers inserted for synchronization.
|
||||
const ORDERED = Self::READ_ALL.bits | Self::COPY_DST.bits | Self::COLOR_TARGET.bits | Self::DEPTH_STENCIL_WRITE.bits;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Alignments {
|
||||
/// The alignment of the start of the buffer used as a GPU copy source.
|
||||
pub buffer_copy_offset: wgt::BufferSize,
|
||||
/// The alignment of the row pitch of the texture data stored in a buffer that is
|
||||
/// used in a GPU copy operation.
|
||||
pub buffer_copy_pitch: wgt::BufferSize,
|
||||
pub storage_buffer_offset: wgt::BufferSize,
|
||||
pub uniform_buffer_offset: wgt::BufferSize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Capabilities {
|
||||
pub limits: wgt::Limits,
|
||||
pub alignments: Alignments,
|
||||
pub downlevel: wgt::DownlevelCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExposedAdapter<A: Api> {
|
||||
pub adapter: A::Adapter,
|
||||
pub info: wgt::AdapterInfo,
|
||||
pub features: wgt::Features,
|
||||
pub capabilities: Capabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OpenDevice<A: Api> {
|
||||
pub device: A::Device,
|
||||
pub queue: A::Queue,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextureViewDescriptor<L> {
|
||||
pub label: L,
|
||||
@ -227,10 +390,9 @@ impl<L> TextureViewDescriptor<L> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a [`Sampler`]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SamplerDescriptor<L> {
|
||||
pub label: L,
|
||||
pub struct SamplerDescriptor<'a> {
|
||||
pub label: Label<'a>,
|
||||
pub address_modes: [wgt::AddressMode; 3],
|
||||
pub mag_filter: wgt::FilterMode,
|
||||
pub min_filter: wgt::FilterMode,
|
||||
@ -241,6 +403,143 @@ pub struct SamplerDescriptor<L> {
|
||||
pub border_color: Option<wgt::SamplerBorderColor>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BindGroupLayoutDescriptor<'a> {
|
||||
pub label: Label<'a>,
|
||||
pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PipelineLayoutDescriptor<'a, A: Api> {
|
||||
pub label: Label<'a>,
|
||||
pub bind_group_layouts: Cow<'a, [&'a A::BindGroupLayout]>,
|
||||
pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BufferBinding<'a, A: Api> {
|
||||
pub buffer: &'a A::Buffer,
|
||||
pub offset: wgt::BufferAddress,
|
||||
pub size: Option<wgt::BufferSize>,
|
||||
}
|
||||
|
||||
// Rust gets confused about the impl requirements for `A`
|
||||
impl<A: Api> Clone for BufferBinding<'_, A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
buffer: self.buffer,
|
||||
offset: self.offset,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BindingResource<'a, A: Api> {
|
||||
Buffers(SmallVec<[BufferBinding<'a, A>; 1]>),
|
||||
Sampler(&'a A::Sampler),
|
||||
TextureViews(SmallVec<[&'a A::TextureView; 1]>, TextureUse),
|
||||
}
|
||||
|
||||
// Rust gets confused about the impl requirements for `A`
|
||||
impl<A: Api> Clone for BindingResource<'_, A> {
|
||||
fn clone(&self) -> Self {
|
||||
match *self {
|
||||
Self::Buffers(ref slice) => Self::Buffers(slice.clone()),
|
||||
Self::Sampler(sampler) => Self::Sampler(sampler),
|
||||
Self::TextureViews(ref slice, usage) => Self::TextureViews(slice.clone(), usage),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BindGroupEntry<'a, A: Api> {
|
||||
pub binding: u32,
|
||||
pub resource: BindingResource<'a, A>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BindGroupDescriptor<'a, A: Api> {
|
||||
pub label: Label<'a>,
|
||||
pub layout: &'a A::BindGroupLayout,
|
||||
pub entries: Cow<'a, [BindGroupEntry<'a, A>]>,
|
||||
}
|
||||
|
||||
/// Naga shader module.
|
||||
pub struct NagaShader {
|
||||
/// Shader module IR.
|
||||
pub module: naga::Module,
|
||||
/// Analysis information of the module.
|
||||
pub info: naga::valid::ModuleInfo,
|
||||
}
|
||||
|
||||
pub struct ShaderModuleDescriptor<'a> {
|
||||
pub label: Label<'a>,
|
||||
}
|
||||
|
||||
/// Describes a programmable pipeline stage.
|
||||
#[derive(Debug)]
|
||||
pub struct ProgrammableStage<'a, A: Api> {
|
||||
/// The compiled shader module for this stage.
|
||||
pub module: &'a A::ShaderModule,
|
||||
/// The name of the entry point in the compiled shader. There must be a function that returns
|
||||
/// void with this name in the shader.
|
||||
pub entry_point: Cow<'a, str>,
|
||||
}
|
||||
|
||||
// Rust gets confused about the impl requirements for `A`
|
||||
impl<A: Api> Clone for ProgrammableStage<'_, A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
module: self.module,
|
||||
entry_point: self.entry_point.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a compute pipeline.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComputePipelineDescriptor<'a, A: Api> {
|
||||
pub label: Label<'a>,
|
||||
/// The layout of bind groups for this pipeline.
|
||||
pub layout: &'a A::PipelineLayout,
|
||||
/// The compiled compute stage and its entry point.
|
||||
pub stage: ProgrammableStage<'a, A>,
|
||||
}
|
||||
|
||||
/// Describes how the vertex buffer is interpreted.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VertexBufferLayout<'a> {
|
||||
/// The stride, in bytes, between elements of this buffer.
|
||||
pub array_stride: wgt::BufferAddress,
|
||||
/// How often this vertex buffer is "stepped" forward.
|
||||
pub step_mode: wgt::InputStepMode,
|
||||
/// The list of attributes which comprise a single vertex.
|
||||
pub attributes: Cow<'a, [wgt::VertexAttribute]>,
|
||||
}
|
||||
|
||||
/// Describes a render (graphics) pipeline.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderPipelineDescriptor<'a, A: Api> {
|
||||
pub label: Label<'a>,
|
||||
/// The layout of bind groups for this pipeline.
|
||||
pub layout: &'a A::PipelineLayout,
|
||||
/// The format of any vertex buffers used with this pipeline.
|
||||
pub vertex_buffers: Cow<'a, [VertexBufferLayout<'a>]>,
|
||||
/// The vertex stage for this pipeline.
|
||||
pub vertex_stage: ProgrammableStage<'a, A>,
|
||||
/// The properties of the pipeline at the primitive assembly and rasterization level.
|
||||
pub primitive: wgt::PrimitiveState,
|
||||
/// The effect of draw calls on the depth and stencil aspects of the output target, if any.
|
||||
pub depth_stencil: Option<wgt::DepthStencilState>,
|
||||
/// The multi-sampling properties of the pipeline.
|
||||
pub multisample: wgt::MultisampleState,
|
||||
/// The fragment stage for this pipeline.
|
||||
pub fragment_stage: ProgrammableStage<'a, A>,
|
||||
/// The effect of draw calls on the color aspect of the output target.
|
||||
pub color_targets: Cow<'a, [wgt::ColorTargetState]>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_limits() {
|
||||
let limits = wgt::Limits::default();
|
||||
|
Loading…
Reference in New Issue
Block a user