Skip to content

Commit b235c47

Browse files
committed
render something
1 parent 174a0e9 commit b235c47

File tree

4 files changed

+336
-14
lines changed

4 files changed

+336
-14
lines changed

sugarloaf/src/components/rich_text/image_cache/cache.rs

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ pub struct WgpuImageCache {
6868

6969
#[derive(Debug)]
7070
pub struct MetalImageCache {
71-
// Metal-specific texture fields will be added here
71+
mask_texture: metal::Texture,
72+
color_texture: metal::Texture,
7273
}
7374

7475
pub struct ImageCache {
@@ -147,9 +148,26 @@ impl ImageCache {
147148
color_texture_view,
148149
})
149150
}
150-
ContextType::Metal(_metal_context) => {
151+
ContextType::Metal(metal_context) => {
152+
let mask_descriptor = metal::TextureDescriptor::new();
153+
mask_descriptor.set_pixel_format(metal::MTLPixelFormat::R8Unorm);
154+
mask_descriptor.set_width(max_texture_size as u64);
155+
mask_descriptor.set_height(max_texture_size as u64);
156+
mask_descriptor.set_usage(metal::MTLTextureUsage::ShaderRead | metal::MTLTextureUsage::ShaderWrite);
157+
let mask_texture = metal_context.device.new_texture(&mask_descriptor);
158+
mask_texture.set_label("Sugarloaf Rich Text Mask Atlas");
159+
160+
let color_descriptor = metal::TextureDescriptor::new();
161+
color_descriptor.set_pixel_format(metal::MTLPixelFormat::RGBA8Unorm);
162+
color_descriptor.set_width(max_texture_size as u64);
163+
color_descriptor.set_height(max_texture_size as u64);
164+
color_descriptor.set_usage(metal::MTLTextureUsage::ShaderRead | metal::MTLTextureUsage::ShaderWrite);
165+
let color_texture = metal_context.device.new_texture(&color_descriptor);
166+
color_texture.set_label("Sugarloaf Rich Text Color Atlas");
167+
151168
ImageCacheType::Metal(MetalImageCache {
152-
// Metal texture creation will be implemented here
169+
mask_texture,
170+
color_texture,
153171
})
154172
}
155173
};
@@ -390,9 +408,56 @@ impl ImageCache {
390408
}
391409
}
392410
ContextType::Metal(_metal_context) => {
393-
// Metal texture updates will be implemented here
394-
if let ImageCacheType::Metal(_metal_cache) = &self.cache_type {
395-
// Metal implementation for processing atlases
411+
if let ImageCacheType::Metal(metal_cache) = &self.cache_type {
412+
// Process mask atlas
413+
if self.mask_atlas.dirty {
414+
println!("Metal: Updating mask atlas with {} bytes", self.mask_atlas.buffer.len());
415+
let region = metal::MTLRegion {
416+
origin: metal::MTLOrigin { x: 0, y: 0, z: 0 },
417+
size: metal::MTLSize {
418+
width: self.max_texture_size as u64,
419+
height: self.max_texture_size as u64,
420+
depth: 1,
421+
},
422+
};
423+
424+
metal_cache.mask_texture.replace_region(
425+
region,
426+
0,
427+
self.mask_atlas.buffer.as_ptr() as *const std::ffi::c_void,
428+
self.max_texture_size as u64 * 1, // 1 byte per pixel for R8
429+
);
430+
431+
self.mask_atlas.fresh = false;
432+
self.mask_atlas.dirty = false;
433+
} else {
434+
println!("Metal: Mask atlas not dirty, no update needed");
435+
}
436+
437+
// Process color atlas
438+
if self.color_atlas.dirty {
439+
println!("Metal: Updating color atlas with {} bytes", self.color_atlas.buffer.len());
440+
let region = metal::MTLRegion {
441+
origin: metal::MTLOrigin { x: 0, y: 0, z: 0 },
442+
size: metal::MTLSize {
443+
width: self.max_texture_size as u64,
444+
height: self.max_texture_size as u64,
445+
depth: 1,
446+
},
447+
};
448+
449+
metal_cache.color_texture.replace_region(
450+
region,
451+
0,
452+
self.color_atlas.buffer.as_ptr() as *const std::ffi::c_void,
453+
self.max_texture_size as u64 * 4, // 4 bytes per pixel for RGBA8
454+
);
455+
456+
self.color_atlas.fresh = false;
457+
self.color_atlas.dirty = false;
458+
} else {
459+
println!("Metal: Color atlas not dirty, no update needed");
460+
}
396461
}
397462
}
398463
}
@@ -408,6 +473,17 @@ impl ImageCache {
408473
ImageCacheType::Metal(_) => None,
409474
}
410475
}
476+
477+
/// Get Metal textures for Metal rendering
478+
pub fn get_metal_textures(&self) -> Option<(&metal::Texture, &metal::Texture)> {
479+
match &self.cache_type {
480+
ImageCacheType::Wgpu(_) => None,
481+
ImageCacheType::Metal(metal_cache) => Some((
482+
&metal_cache.color_texture,
483+
&metal_cache.mask_texture,
484+
)),
485+
}
486+
}
411487
}
412488

413489
struct FillParams {

sugarloaf/src/components/rich_text/mod.rs

Lines changed: 200 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::collections::HashSet;
2323
use std::{borrow::Cow, mem};
2424
use text::{Glyph, TextRunStyle};
2525
use wgpu::util::DeviceExt;
26+
use metal::*;
2627

2728
pub const BLEND: Option<wgpu::BlendState> = Some(wgpu::BlendState {
2829
color: wgpu::BlendComponent {
@@ -56,11 +57,195 @@ pub struct WgpuRichTextBrush {
5657
textures_version: usize,
5758
}
5859

60+
#[repr(C)]
61+
#[derive(Debug, Copy, Clone)]
62+
struct Globals {
63+
transform: [f32; 16],
64+
}
65+
5966
#[derive(Debug)]
6067
pub struct MetalRichTextBrush {
61-
// Metal-specific fields will be added here
62-
current_transform: [f32; 16],
68+
pipeline_state: RenderPipelineState,
69+
vertex_buffer: Buffer,
70+
uniform_buffer: Buffer,
71+
sampler: SamplerState,
6372
supported_vertex_buffer: usize,
73+
current_transform: [f32; 16],
74+
}
75+
76+
impl MetalRichTextBrush {
77+
pub fn new(context: &MetalContext) -> Self {
78+
let supported_vertex_buffer = 500;
79+
80+
// Create Metal shader library
81+
let shader_source = include_str!("rich_text.metal");
82+
let library = context
83+
.device
84+
.new_library_with_source(shader_source, &CompileOptions::new())
85+
.expect("Failed to create shader library");
86+
87+
let vertex_function = library
88+
.get_function("vs_main", None)
89+
.expect("Failed to get vertex function");
90+
let fragment_function = library
91+
.get_function("fs_main", None)
92+
.expect("Failed to get fragment function");
93+
94+
// Create vertex descriptor for rich text rendering
95+
let vertex_descriptor = VertexDescriptor::new();
96+
let attributes = vertex_descriptor.attributes();
97+
98+
// Position (attribute 0) - vec4<f32>
99+
attributes.object_at(0).unwrap().set_format(MTLVertexFormat::Float4);
100+
attributes.object_at(0).unwrap().set_offset(0);
101+
attributes.object_at(0).unwrap().set_buffer_index(0);
102+
103+
// Color (attribute 1) - vec4<f32>
104+
attributes.object_at(1).unwrap().set_format(MTLVertexFormat::Float4);
105+
attributes.object_at(1).unwrap().set_offset(16);
106+
attributes.object_at(1).unwrap().set_buffer_index(0);
107+
108+
// UV (attribute 2) - vec2<f32>
109+
attributes.object_at(2).unwrap().set_format(MTLVertexFormat::Float2);
110+
attributes.object_at(2).unwrap().set_offset(32);
111+
attributes.object_at(2).unwrap().set_buffer_index(0);
112+
113+
// Layers (attribute 3) - vec2<i32>
114+
attributes.object_at(3).unwrap().set_format(MTLVertexFormat::Int2);
115+
attributes.object_at(3).unwrap().set_offset(40);
116+
attributes.object_at(3).unwrap().set_buffer_index(0);
117+
118+
// Set up buffer layout
119+
let layouts = vertex_descriptor.layouts();
120+
layouts.object_at(0).unwrap().set_stride(std::mem::size_of::<Vertex>() as u64);
121+
layouts.object_at(0).unwrap().set_step_function(MTLVertexStepFunction::PerVertex);
122+
layouts.object_at(0).unwrap().set_step_rate(1);
123+
124+
// Create render pipeline
125+
let pipeline_descriptor = RenderPipelineDescriptor::new();
126+
pipeline_descriptor.set_vertex_function(Some(&vertex_function));
127+
pipeline_descriptor.set_fragment_function(Some(&fragment_function));
128+
pipeline_descriptor.set_vertex_descriptor(Some(&vertex_descriptor));
129+
130+
// Set up blending for text rendering
131+
let color_attachment = pipeline_descriptor.color_attachments().object_at(0).unwrap();
132+
color_attachment.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
133+
color_attachment.set_blending_enabled(true);
134+
color_attachment.set_source_rgb_blend_factor(MTLBlendFactor::SourceAlpha);
135+
color_attachment.set_destination_rgb_blend_factor(MTLBlendFactor::OneMinusSourceAlpha);
136+
color_attachment.set_source_alpha_blend_factor(MTLBlendFactor::SourceAlpha);
137+
color_attachment.set_destination_alpha_blend_factor(MTLBlendFactor::OneMinusSourceAlpha);
138+
139+
let pipeline_state = context
140+
.device
141+
.new_render_pipeline_state(&pipeline_descriptor)
142+
.expect("Failed to create render pipeline state");
143+
144+
// Create vertex buffer
145+
let vertex_buffer = context.device.new_buffer(
146+
(mem::size_of::<Vertex>() * supported_vertex_buffer) as u64,
147+
MTLResourceOptions::StorageModeShared,
148+
);
149+
vertex_buffer.set_label("sugarloaf::rich_text vertex buffer");
150+
151+
// Create uniform buffer
152+
let uniform_buffer = context.device.new_buffer(
153+
mem::size_of::<Globals>() as u64,
154+
MTLResourceOptions::StorageModeShared,
155+
);
156+
uniform_buffer.set_label("sugarloaf::rich_text uniform buffer");
157+
158+
// Create sampler for texture sampling
159+
let sampler_descriptor = SamplerDescriptor::new();
160+
sampler_descriptor.set_min_filter(MTLSamplerMinMagFilter::Linear);
161+
sampler_descriptor.set_mag_filter(MTLSamplerMinMagFilter::Linear);
162+
sampler_descriptor.set_mip_filter(MTLSamplerMipFilter::Linear);
163+
let sampler = context.device.new_sampler(&sampler_descriptor);
164+
165+
Self {
166+
pipeline_state,
167+
vertex_buffer,
168+
uniform_buffer,
169+
sampler,
170+
supported_vertex_buffer,
171+
current_transform: [0.0; 16],
172+
}
173+
}
174+
175+
pub fn resize(&mut self, transform: [f32; 16]) {
176+
if self.current_transform != transform {
177+
let globals = Globals { transform };
178+
let contents = self.uniform_buffer.contents() as *mut Globals;
179+
unsafe {
180+
*contents = globals;
181+
}
182+
self.current_transform = transform;
183+
}
184+
}
185+
186+
pub fn render(
187+
&mut self,
188+
vertices: &[Vertex],
189+
images: &ImageCache,
190+
render_encoder: &RenderCommandEncoderRef,
191+
context: &MetalContext, // Add context to get proper window size
192+
) {
193+
println!("Metal rich text: {} vertices - ATLAS CONNECTION ISSUE: layers=[0,0], uv=[0,0]", vertices.len());
194+
195+
if vertices.is_empty() {
196+
return;
197+
}
198+
199+
// Expand vertex buffer if needed
200+
if vertices.len() > self.supported_vertex_buffer {
201+
// For now, just skip rendering if too many vertices
202+
// In production, you'd want to expand the buffer
203+
return;
204+
}
205+
206+
// Copy vertex data to buffer
207+
let vertex_data = self.vertex_buffer.contents() as *mut Vertex;
208+
unsafe {
209+
std::ptr::copy_nonoverlapping(vertices.as_ptr(), vertex_data, vertices.len());
210+
}
211+
212+
// Set up render state
213+
render_encoder.set_render_pipeline_state(&self.pipeline_state);
214+
render_encoder.set_vertex_buffer(0, Some(&self.vertex_buffer), 0);
215+
216+
// Use proper orthographic projection matching the quad renderer
217+
use crate::components::core::orthographic_projection;
218+
let transform = orthographic_projection(context.size.width, context.size.height);
219+
220+
// Update uniform buffer with correct transform
221+
let uniform_data = self.uniform_buffer.contents() as *mut [f32; 16];
222+
unsafe {
223+
*uniform_data = transform;
224+
}
225+
226+
render_encoder.set_vertex_buffer(1, Some(&self.uniform_buffer), 0);
227+
228+
// Set sampler
229+
render_encoder.set_fragment_sampler_state(0, Some(&self.sampler));
230+
231+
// Set Metal textures for glyph atlas
232+
if let Some((color_texture, mask_texture)) = images.get_metal_textures() {
233+
render_encoder.set_fragment_texture(0, Some(color_texture));
234+
render_encoder.set_fragment_texture(1, Some(mask_texture));
235+
}
236+
237+
// Draw vertices - make sure we're drawing the right number for triangles
238+
println!("Metal: Drawing {} vertices at positions: {:?}",
239+
vertices.len(),
240+
vertices.iter().take(6).map(|v| v.pos).collect::<Vec<_>>());
241+
242+
// Use triangle primitive type - each set of 3 vertices forms a triangle
243+
render_encoder.draw_primitives(
244+
MTLPrimitiveType::Triangle,
245+
0,
246+
vertices.len() as u64,
247+
);
248+
}
64249
}
65250

66251
pub struct RichTextBrush {
@@ -78,11 +263,8 @@ impl RichTextBrush {
78263
ContextType::Wgpu(wgpu_context) => {
79264
RichTextBrushType::Wgpu(WgpuRichTextBrush::new(wgpu_context))
80265
}
81-
ContextType::Metal(_metal_context) => {
82-
RichTextBrushType::Metal(MetalRichTextBrush {
83-
current_transform: [0.0; 16],
84-
supported_vertex_buffer: 500,
85-
})
266+
ContextType::Metal(metal_context) => {
267+
RichTextBrushType::Metal(MetalRichTextBrush::new(metal_context))
86268
}
87269
};
88270

@@ -625,6 +807,16 @@ impl RichTextBrush {
625807
}
626808
}
627809

810+
pub fn render_metal(
811+
&mut self,
812+
context: &MetalContext, // Add context parameter
813+
render_encoder: &metal::RenderCommandEncoderRef,
814+
) {
815+
if let RichTextBrushType::Metal(brush) = &mut self.brush_type {
816+
brush.render(&self.vertices, &self.images, render_encoder, context);
817+
}
818+
}
819+
628820
pub fn resize(&mut self, context: &mut Context) {
629821
let transform = match &context.inner {
630822
ContextType::Wgpu(wgpu_ctx) => {
@@ -652,7 +844,7 @@ impl RichTextBrush {
652844
}
653845
}
654846
RichTextBrushType::Metal(metal_brush) => {
655-
metal_brush.current_transform = transform;
847+
metal_brush.resize(transform);
656848
}
657849
}
658850
}

0 commit comments

Comments
 (0)