Merge pull request #376 from mahkoh/jorth/vulkan-fixes
Various vulkan fixes
This commit is contained in:
commit
b00f3a2ffe
6 changed files with 71 additions and 58 deletions
|
|
@ -257,23 +257,8 @@ pub enum ResetStatus {
|
||||||
pub trait GfxFramebuffer: Debug {
|
pub trait GfxFramebuffer: Debug {
|
||||||
fn physical_size(&self) -> (i32, i32);
|
fn physical_size(&self) -> (i32, i32);
|
||||||
|
|
||||||
fn full_region(&self) -> Region {
|
|
||||||
let (width, height) = self.physical_size();
|
|
||||||
Region::new2(Rect::new_sized_unchecked(0, 0, width, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(
|
|
||||||
&self,
|
|
||||||
acquire_sync: AcquireSync,
|
|
||||||
release_sync: ReleaseSync,
|
|
||||||
ops: &[GfxApiOpt],
|
|
||||||
clear: Option<&Color>,
|
|
||||||
) -> Result<Option<SyncFile>, GfxError> {
|
|
||||||
self.render_with_region(acquire_sync, release_sync, ops, clear, &self.full_region())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_with_region(
|
fn render_with_region(
|
||||||
&self,
|
self: Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
ops: &[GfxApiOpt],
|
ops: &[GfxApiOpt],
|
||||||
|
|
@ -300,8 +285,24 @@ pub trait GfxInternalFramebuffer: GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn GfxFramebuffer {
|
impl dyn GfxFramebuffer {
|
||||||
|
pub fn render(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
acquire_sync: AcquireSync,
|
||||||
|
release_sync: ReleaseSync,
|
||||||
|
ops: &[GfxApiOpt],
|
||||||
|
clear: Option<&Color>,
|
||||||
|
) -> Result<Option<SyncFile>, GfxError> {
|
||||||
|
self.clone()
|
||||||
|
.render_with_region(acquire_sync, release_sync, ops, clear, &self.full_region())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_region(&self) -> Region {
|
||||||
|
let (width, height) = self.physical_size();
|
||||||
|
Region::new2(Rect::new_sized_unchecked(0, 0, width, height))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(
|
pub fn clear(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
) -> Result<Option<SyncFile>, GfxError> {
|
) -> Result<Option<SyncFile>, GfxError> {
|
||||||
|
|
@ -309,7 +310,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_with(
|
pub fn clear_with(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
r: f32,
|
r: f32,
|
||||||
|
|
@ -334,7 +335,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_texture(
|
pub fn copy_texture(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
fb_acquire_sync: AcquireSync,
|
fb_acquire_sync: AcquireSync,
|
||||||
fb_release_sync: ReleaseSync,
|
fb_release_sync: ReleaseSync,
|
||||||
texture: &Rc<dyn GfxTexture>,
|
texture: &Rc<dyn GfxTexture>,
|
||||||
|
|
@ -365,7 +366,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_custom(
|
pub fn render_custom(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
scale: Scale,
|
scale: Scale,
|
||||||
|
|
@ -407,13 +408,13 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_render_pass(
|
pub fn perform_render_pass(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
pass: &GfxRenderPass,
|
pass: &GfxRenderPass,
|
||||||
region: &Region,
|
region: &Region,
|
||||||
) -> Result<Option<SyncFile>, GfxError> {
|
) -> Result<Option<SyncFile>, GfxError> {
|
||||||
self.render_with_region(
|
self.clone().render_with_region(
|
||||||
acquire_sync,
|
acquire_sync,
|
||||||
release_sync,
|
release_sync,
|
||||||
&pass.ops,
|
&pass.ops,
|
||||||
|
|
@ -423,7 +424,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_output(
|
pub fn render_output(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
node: &OutputNode,
|
node: &OutputNode,
|
||||||
|
|
@ -449,7 +450,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_node(
|
pub fn render_node(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
node: &dyn Node,
|
node: &dyn Node,
|
||||||
|
|
@ -478,7 +479,7 @@ impl dyn GfxFramebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_hardware_cursor(
|
pub fn render_hardware_cursor(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
cursor: &dyn Cursor,
|
cursor: &dyn Cursor,
|
||||||
|
|
|
||||||
|
|
@ -100,14 +100,16 @@ impl GfxFramebuffer for Framebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_with_region(
|
fn render_with_region(
|
||||||
&self,
|
self: Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
_release_sync: ReleaseSync,
|
_release_sync: ReleaseSync,
|
||||||
ops: &[GfxApiOpt],
|
ops: &[GfxApiOpt],
|
||||||
clear: Option<&Color>,
|
clear: Option<&Color>,
|
||||||
_region: &Region,
|
_region: &Region,
|
||||||
) -> Result<Option<SyncFile>, GfxError> {
|
) -> Result<Option<SyncFile>, GfxError> {
|
||||||
self.render(acquire_sync, ops, clear).map_err(|e| e.into())
|
(*self)
|
||||||
|
.render(acquire_sync, ops, clear)
|
||||||
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format(&self) -> &'static Format {
|
fn format(&self) -> &'static Format {
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ pub struct VulkanImage {
|
||||||
pub(super) shader_read_only_optimal_descriptor: Box<[u8]>,
|
pub(super) shader_read_only_optimal_descriptor: Box<[u8]>,
|
||||||
pub(super) descriptor_buffer_version: Cell<u64>,
|
pub(super) descriptor_buffer_version: Cell<u64>,
|
||||||
pub(super) descriptor_buffer_offset: Cell<DeviceSize>,
|
pub(super) descriptor_buffer_offset: Cell<DeviceSize>,
|
||||||
|
pub(super) execution_version: Cell<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
|
@ -452,6 +453,7 @@ impl VulkanDmaBufImageTemplate {
|
||||||
.sampler_read_only_descriptor(texture_view),
|
.sampler_read_only_descriptor(texture_view),
|
||||||
descriptor_buffer_version: Cell::new(0),
|
descriptor_buffer_version: Cell::new(0),
|
||||||
descriptor_buffer_offset: Cell::new(0),
|
descriptor_buffer_offset: Cell::new(0),
|
||||||
|
execution_version: Cell::new(0),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,7 +533,7 @@ impl GfxFramebuffer for VulkanImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_with_region(
|
fn render_with_region(
|
||||||
&self,
|
self: Rc<Self>,
|
||||||
acquire_sync: AcquireSync,
|
acquire_sync: AcquireSync,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
ops: &[GfxApiOpt],
|
ops: &[GfxApiOpt],
|
||||||
|
|
@ -539,7 +541,7 @@ impl GfxFramebuffer for VulkanImage {
|
||||||
region: &Region,
|
region: &Region,
|
||||||
) -> Result<Option<SyncFile>, GfxError> {
|
) -> Result<Option<SyncFile>, GfxError> {
|
||||||
self.renderer
|
self.renderer
|
||||||
.execute(self, acquire_sync, release_sync, ops, clear, region)
|
.execute(&self, acquire_sync, release_sync, ops, clear, region)
|
||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ use {
|
||||||
SubmitInfo2, Viewport, WriteDescriptorSet,
|
SubmitInfo2, Viewport, WriteDescriptorSet,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isnt::std_1::collections::IsntHashMapExt,
|
isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt},
|
||||||
linearize::{Linearize, StaticMap, static_map},
|
linearize::{Linearize, StaticMap, static_map},
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -87,7 +87,6 @@ pub struct VulkanRenderer {
|
||||||
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
||||||
pub(super) sampler: Rc<VulkanSampler>,
|
pub(super) sampler: Rc<VulkanSampler>,
|
||||||
pub(super) tex_sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
|
pub(super) tex_sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
|
||||||
pub(super) descriptor_buffer_version: NumCell<u64>,
|
|
||||||
pub(super) tex_descriptor_buffer_writer: RefCell<VulkanDescriptorBufferWriter>,
|
pub(super) tex_descriptor_buffer_writer: RefCell<VulkanDescriptorBufferWriter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +157,7 @@ pub(super) struct PendingFrame {
|
||||||
point: u64,
|
point: u64,
|
||||||
renderer: Rc<VulkanRenderer>,
|
renderer: Rc<VulkanRenderer>,
|
||||||
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
|
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
|
||||||
|
_fb: Rc<VulkanImage>,
|
||||||
_textures: Vec<UsedTexture>,
|
_textures: Vec<UsedTexture>,
|
||||||
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
|
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
|
||||||
waiter: Cell<Option<SpawnedFuture<()>>>,
|
waiter: Cell<Option<SpawnedFuture<()>>>,
|
||||||
|
|
@ -253,7 +253,6 @@ impl VulkanDevice {
|
||||||
shm_allocator,
|
shm_allocator,
|
||||||
sampler,
|
sampler,
|
||||||
tex_sampler_descriptor_buffer_cache: tex_descriptor_buffer_cache,
|
tex_sampler_descriptor_buffer_cache: tex_descriptor_buffer_cache,
|
||||||
descriptor_buffer_version: Default::default(),
|
|
||||||
tex_descriptor_buffer_writer,
|
tex_descriptor_buffer_writer,
|
||||||
});
|
});
|
||||||
render.get_or_create_pipelines(XRGB8888.vk_format)?;
|
render.get_or_create_pipelines(XRGB8888.vk_format)?;
|
||||||
|
|
@ -326,7 +325,7 @@ impl VulkanRenderer {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
zone!("create_descriptor_buffer");
|
zone!("create_descriptor_buffer");
|
||||||
let version = self.descriptor_buffer_version.add_fetch(1);
|
let version = self.allocate_point();
|
||||||
let memory = &mut *self.memory.borrow_mut();
|
let memory = &mut *self.memory.borrow_mut();
|
||||||
let writer = &mut *self.tex_descriptor_buffer_writer.borrow_mut();
|
let writer = &mut *self.tex_descriptor_buffer_writer.borrow_mut();
|
||||||
writer.clear();
|
writer.clear();
|
||||||
|
|
@ -367,12 +366,16 @@ impl VulkanRenderer {
|
||||||
let mut memory = self.memory.borrow_mut();
|
let mut memory = self.memory.borrow_mut();
|
||||||
memory.dmabuf_sample.clear();
|
memory.dmabuf_sample.clear();
|
||||||
memory.queue_transfer.clear();
|
memory.queue_transfer.clear();
|
||||||
|
let execution = self.allocate_point();
|
||||||
for cmd in opts {
|
for cmd in opts {
|
||||||
if let GfxApiOpt::CopyTexture(c) = cmd {
|
if let GfxApiOpt::CopyTexture(c) = cmd {
|
||||||
let tex = c.tex.clone().into_vk(&self.device.device);
|
let tex = c.tex.clone().into_vk(&self.device.device);
|
||||||
if tex.contents_are_undefined.get() {
|
if tex.contents_are_undefined.get() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if tex.execution_version.replace(execution) == execution {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
|
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
|
||||||
QueueTransfer::Unnecessary => {}
|
QueueTransfer::Unnecessary => {}
|
||||||
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
|
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
|
||||||
|
|
@ -519,7 +522,7 @@ impl VulkanRenderer {
|
||||||
let rendering_attachment_info = {
|
let rendering_attachment_info = {
|
||||||
let mut rai = RenderingAttachmentInfo::default()
|
let mut rai = RenderingAttachmentInfo::default()
|
||||||
.image_view(fb.render_view.unwrap_or(fb.texture_view))
|
.image_view(fb.render_view.unwrap_or(fb.texture_view))
|
||||||
.image_layout(ImageLayout::GENERAL)
|
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
.load_op(AttachmentLoadOp::LOAD)
|
.load_op(AttachmentLoadOp::LOAD)
|
||||||
.store_op(AttachmentStoreOp::STORE);
|
.store_op(AttachmentStoreOp::STORE);
|
||||||
if let Some(clear) = load_clear {
|
if let Some(clear) = load_clear {
|
||||||
|
|
@ -540,25 +543,27 @@ impl VulkanRenderer {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.device.cmd_begin_rendering(buf, &rendering_info);
|
self.device.device.cmd_begin_rendering(buf, &rendering_info);
|
||||||
}
|
}
|
||||||
if let Some(clear) = manual_clear {
|
if memory.paint_regions.is_not_empty() {
|
||||||
let clear_attachment = ClearAttachment::default()
|
if let Some(clear) = manual_clear {
|
||||||
.color_attachment(0)
|
let clear_attachment = ClearAttachment::default()
|
||||||
.clear_value(clear)
|
.color_attachment(0)
|
||||||
.aspect_mask(ImageAspectFlags::COLOR);
|
.clear_value(clear)
|
||||||
memory.clear_rects.clear();
|
.aspect_mask(ImageAspectFlags::COLOR);
|
||||||
for region in &memory.paint_regions {
|
memory.clear_rects.clear();
|
||||||
memory.clear_rects.push(ClearRect {
|
for region in &memory.paint_regions {
|
||||||
rect: region.rect,
|
memory.clear_rects.push(ClearRect {
|
||||||
base_array_layer: 0,
|
rect: region.rect,
|
||||||
layer_count: 1,
|
base_array_layer: 0,
|
||||||
});
|
layer_count: 1,
|
||||||
}
|
});
|
||||||
unsafe {
|
}
|
||||||
self.device.device.cmd_clear_attachments(
|
unsafe {
|
||||||
buf,
|
self.device.device.cmd_clear_attachments(
|
||||||
&[clear_attachment],
|
buf,
|
||||||
&memory.clear_rects,
|
&[clear_attachment],
|
||||||
);
|
&memory.clear_rects,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1008,7 +1013,7 @@ impl VulkanRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pending_frame(self: &Rc<Self>, buf: Rc<VulkanCommandBuffer>) {
|
fn create_pending_frame(self: &Rc<Self>, buf: Rc<VulkanCommandBuffer>, fb: &Rc<VulkanImage>) {
|
||||||
zone!("create_pending_frame");
|
zone!("create_pending_frame");
|
||||||
let point = self.allocate_point();
|
let point = self.allocate_point();
|
||||||
let mut memory = self.memory.borrow_mut();
|
let mut memory = self.memory.borrow_mut();
|
||||||
|
|
@ -1016,6 +1021,7 @@ impl VulkanRenderer {
|
||||||
point,
|
point,
|
||||||
renderer: self.clone(),
|
renderer: self.clone(),
|
||||||
cmd: Cell::new(Some(buf)),
|
cmd: Cell::new(Some(buf)),
|
||||||
|
_fb: fb.clone(),
|
||||||
_textures: mem::take(&mut memory.textures),
|
_textures: mem::take(&mut memory.textures),
|
||||||
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
|
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
|
||||||
waiter: Cell::new(None),
|
waiter: Cell::new(None),
|
||||||
|
|
@ -1037,7 +1043,7 @@ impl VulkanRenderer {
|
||||||
|
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
fb: &VulkanImage,
|
fb: &Rc<VulkanImage>,
|
||||||
fb_acquire_sync: AcquireSync,
|
fb_acquire_sync: AcquireSync,
|
||||||
fb_release_sync: ReleaseSync,
|
fb_release_sync: ReleaseSync,
|
||||||
opts: &[GfxApiOpt],
|
opts: &[GfxApiOpt],
|
||||||
|
|
@ -1053,6 +1059,7 @@ impl VulkanRenderer {
|
||||||
memory.queue_transfer.clear();
|
memory.queue_transfer.clear();
|
||||||
memory.wait_semaphores.clear();
|
memory.wait_semaphores.clear();
|
||||||
memory.release_fence.take();
|
memory.release_fence.take();
|
||||||
|
memory.descriptor_buffer.take();
|
||||||
memory.release_sync_file.take()
|
memory.release_sync_file.take()
|
||||||
};
|
};
|
||||||
res.map(|_| sync_file)
|
res.map(|_| sync_file)
|
||||||
|
|
@ -1102,7 +1109,7 @@ impl VulkanRenderer {
|
||||||
|
|
||||||
fn try_execute(
|
fn try_execute(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
fb: &VulkanImage,
|
fb: &Rc<VulkanImage>,
|
||||||
fb_acquire_sync: AcquireSync,
|
fb_acquire_sync: AcquireSync,
|
||||||
fb_release_sync: ReleaseSync,
|
fb_release_sync: ReleaseSync,
|
||||||
opts: &[GfxApiOpt],
|
opts: &[GfxApiOpt],
|
||||||
|
|
@ -1127,7 +1134,7 @@ impl VulkanRenderer {
|
||||||
self.submit(buf.buffer)?;
|
self.submit(buf.buffer)?;
|
||||||
self.import_release_semaphore(fb, fb_release_sync);
|
self.import_release_semaphore(fb, fb_release_sync);
|
||||||
self.store_layouts(fb);
|
self.store_layouts(fb);
|
||||||
self.create_pending_frame(buf);
|
self.create_pending_frame(buf, fb);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,7 @@ impl VulkanRenderer {
|
||||||
shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view),
|
shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view),
|
||||||
descriptor_buffer_version: Cell::new(0),
|
descriptor_buffer_version: Cell::new(0),
|
||||||
descriptor_buffer_offset: Cell::new(0),
|
descriptor_buffer_offset: Cell::new(0),
|
||||||
|
execution_version: Cell::new(0),
|
||||||
});
|
});
|
||||||
let shm = match &img.ty {
|
let shm = match &img.ty {
|
||||||
VulkanImageMemory::DmaBuf(_) => unreachable!(),
|
VulkanImageMemory::DmaBuf(_) => unreachable!(),
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ impl GfxFramebuffer for TestGfxFb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_with_region(
|
fn render_with_region(
|
||||||
&self,
|
self: Rc<Self>,
|
||||||
_acquire_sync: AcquireSync,
|
_acquire_sync: AcquireSync,
|
||||||
_release_sync: ReleaseSync,
|
_release_sync: ReleaseSync,
|
||||||
ops: &[GfxApiOpt],
|
ops: &[GfxApiOpt],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue