Detailed changes
@@ -1839,7 +1839,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.6.0"
-source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
+source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"ash",
"ash-window",
@@ -1871,7 +1871,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
-source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
+source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"proc-macro2",
"quote",
@@ -1881,7 +1881,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.2.0"
-source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
+source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -375,9 +375,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
-blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
+blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
+blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
+blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -27,6 +27,7 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>,
+ path_sample_count: u32,
}
#[cfg(gles)]
@@ -42,10 +43,11 @@ impl BladeAtlasState {
pub struct BladeTextureInfo {
pub size: gpu::Extent,
pub raw_view: gpu::TextureView,
+ pub msaa_view: Option<gpu::TextureView>,
}
impl BladeAtlas {
- pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
+ pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor {
@@ -57,6 +59,7 @@ impl BladeAtlas {
tiles_by_key: Default::default(),
initializations: Vec::new(),
uploads: Vec::new(),
+ path_sample_count,
}))
}
@@ -106,6 +109,7 @@ impl BladeAtlas {
depth: 1,
},
raw_view: texture.raw_view,
+ msaa_view: texture.msaa_view,
}
}
}
@@ -204,6 +208,39 @@ impl BladeAtlasState {
}
}
+ // We currently only enable MSAA for path textures.
+ let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
+ let msaa = self.gpu.create_texture(gpu::TextureDesc {
+ name: "msaa path texture",
+ format,
+ size: gpu::Extent {
+ width: size.width.into(),
+ height: size.height.into(),
+ depth: 1,
+ },
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: self.path_sample_count,
+ dimension: gpu::TextureDimension::D2,
+ usage: gpu::TextureUsage::TARGET,
+ });
+
+ (
+ Some(msaa),
+ Some(self.gpu.create_texture_view(
+ msaa,
+ gpu::TextureViewDesc {
+ name: "msaa texture view",
+ format,
+ dimension: gpu::ViewDimension::D2,
+ subresources: &Default::default(),
+ },
+ )),
+ )
+ } else {
+ (None, None)
+ };
+
let raw = self.gpu.create_texture(gpu::TextureDesc {
name: "atlas",
format,
@@ -240,6 +277,8 @@ impl BladeAtlasState {
format,
raw,
raw_view,
+ msaa,
+ msaa_view,
live_atlas_keys: 0,
};
@@ -354,6 +393,8 @@ struct BladeAtlasTexture {
allocator: BucketedAtlasAllocator,
raw: gpu::Texture,
raw_view: gpu::TextureView,
+ msaa: Option<gpu::Texture>,
+ msaa_view: Option<gpu::TextureView>,
format: gpu::TextureFormat,
live_atlas_keys: u32,
}
@@ -381,6 +422,12 @@ impl BladeAtlasTexture {
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_texture(self.raw);
gpu.destroy_texture_view(self.raw_view);
+ if let Some(msaa) = self.msaa {
+ gpu.destroy_texture(msaa);
+ }
+ if let Some(msaa_view) = self.msaa_view {
+ gpu.destroy_texture_view(msaa_view);
+ }
}
fn bytes_per_pixel(&self) -> u8 {
@@ -7,16 +7,18 @@ use crate::{
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline,
};
+use blade_graphics as gpu;
+use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
-
-use blade_graphics as gpu;
-use blade_util::{BufferBelt, BufferBeltDescriptor};
use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000;
+// Use 4x MSAA, all devices support it.
+// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
+const PATH_SAMPLE_COUNT: u32 = 4;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
@@ -208,7 +210,10 @@ impl BladePipelines {
blend: Some(gpu::BlendState::ADDITIVE),
write_mask: gpu::ColorWrites::default(),
}],
- multisample_state: gpu::MultisampleState::default(),
+ multisample_state: gpu::MultisampleState {
+ sample_count: PATH_SAMPLE_COUNT,
+ ..Default::default()
+ },
}),
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "paths",
@@ -348,7 +353,7 @@ impl BladeRenderer {
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
- let atlas = Arc::new(BladeAtlas::new(&context.gpu));
+ let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
@@ -497,27 +502,38 @@ impl BladeRenderer {
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
- let mut pass = self.command_encoder.render(
+ let frame_view = tex_info.raw_view;
+ let color_target = if let Some(msaa_view) = tex_info.msaa_view {
+ gpu::RenderTarget {
+ view: msaa_view,
+ init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
+ finish_op: gpu::FinishOp::ResolveTo(frame_view),
+ }
+ } else {
+ gpu::RenderTarget {
+ view: frame_view,
+ init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
+ finish_op: gpu::FinishOp::Store,
+ }
+ };
+
+ if let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
- colors: &[gpu::RenderTarget {
- view: tex_info.raw_view,
- init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
- finish_op: gpu::FinishOp::Store,
- }],
+ colors: &[color_target],
depth_stencil: None,
},
- );
-
- let mut encoder = pass.with(&self.pipelines.path_rasterization);
- encoder.bind(
- 0,
- &ShaderPathRasterizationData {
- globals,
- b_path_vertices: vertex_buf,
- },
- );
- encoder.draw(0, vertices.len() as u32, 0, 1);
+ ) {
+ let mut encoder = pass.with(&self.pipelines.path_rasterization);
+ encoder.bind(
+ 0,
+ &ShaderPathRasterizationData {
+ globals,
+ b_path_vertices: vertex_buf,
+ },
+ );
+ encoder.draw(0, vertices.len() as u32, 0, 1);
+ }
}
}
@@ -13,13 +13,14 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
- pub(crate) fn new(device: Device) -> Self {
+ pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
path_textures: Default::default(),
tiles_by_key: Default::default(),
+ path_sample_count,
}))
}
@@ -27,6 +28,10 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone()
}
+ pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
+ self.0.lock().texture(id).msaa_texture.clone()
+ }
+
pub(crate) fn allocate(
&self,
size: Size<DevicePixels>,
@@ -54,6 +59,7 @@ struct MetalAtlasState {
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
+ path_sample_count: u32,
}
impl PlatformAtlas for MetalAtlas {
@@ -176,6 +182,18 @@ impl MetalAtlasState {
texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor);
+ // We currently only enable MSAA for path textures.
+ let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
+ let mut descriptor = texture_descriptor.clone();
+ descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
+ descriptor.set_storage_mode(metal::MTLStorageMode::Private);
+ descriptor.set_sample_count(self.path_sample_count as _);
+ let msaa_texture = self.device.new_texture(&descriptor);
+ Some(msaa_texture)
+ } else {
+ None
+ };
+
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
@@ -191,6 +209,7 @@ impl MetalAtlasState {
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
+ msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0,
};
@@ -217,6 +236,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
+ msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32,
}
@@ -28,6 +28,9 @@ pub(crate) type PointF = crate::Point<f32>;
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
#[cfg(feature = "runtime_shaders")]
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
+// Use 4x MSAA, all devices support it.
+// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
+const PATH_SAMPLE_COUNT: u32 = 4;
pub type Context = Arc<Mutex<InstanceBufferPool>>;
pub type Renderer = MetalRenderer;
@@ -170,6 +173,7 @@ impl MetalRenderer {
"path_rasterization_vertex",
"path_rasterization_fragment",
MTLPixelFormat::R16Float,
+ PATH_SAMPLE_COUNT,
);
let path_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -229,7 +233,7 @@ impl MetalRenderer {
);
let command_queue = device.new_command_queue();
- let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
+ let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));
let core_video_texture_cache =
unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
@@ -531,10 +535,20 @@ impl MetalRenderer {
.unwrap();
let texture = self.sprite_atlas.metal_texture(texture_id);
- color_attachment.set_texture(Some(&texture));
- color_attachment.set_load_action(metal::MTLLoadAction::Clear);
- color_attachment.set_store_action(metal::MTLStoreAction::Store);
+ let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
+
+ if let Some(msaa_texture) = msaa_texture {
+ color_attachment.set_texture(Some(&msaa_texture));
+ color_attachment.set_resolve_texture(Some(&texture));
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
+ } else {
+ color_attachment.set_texture(Some(&texture));
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_store_action(metal::MTLStoreAction::Store);
+ }
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
+
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer(
@@ -1160,6 +1174,7 @@ fn build_path_rasterization_pipeline_state(
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
+ path_sample_count: u32,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
@@ -1172,6 +1187,10 @@ fn build_path_rasterization_pipeline_state(
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+ if path_sample_count > 1 {
+ descriptor.set_raster_sample_count(path_sample_count as _);
+ descriptor.set_alpha_to_coverage_enabled(true);
+ }
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);