1use crate::{DevicePixels, PaintMetalView, PrimitiveBatch, ScaledPixels, Scene, Size};
2use metal::{
3 CommandBufferRef, CommandQueue, Device, MTLLoadAction, MTLStoreAction, RenderCommandEncoderRef,
4};
5
6/// Represents a single render command in the rendering pipeline
7#[derive(Debug)]
8pub enum RenderCommand<'a> {
9 /// Begin a new render pass with the specified configuration
10 BeginRenderPass { descriptor: RenderPassDescriptor },
11 /// Draw a batch of GPUI primitives
12 DrawPrimitives {
13 batch: PrimitiveBatch<'a>,
14 viewport_size: Size<DevicePixels>,
15 },
16 /// Execute custom Metal rendering
17 ExecuteMetalCallback {
18 metal_view: &'a PaintMetalView,
19 viewport_size: Size<DevicePixels>,
20 },
21 /// End the current render pass
22 EndRenderPass,
23}
24
25/// Configuration for a render pass
26#[derive(Clone, Debug)]
27pub struct RenderPassDescriptor {
28 pub texture: metal::Texture,
29 pub load_action: MTLLoadAction,
30 pub store_action: MTLStoreAction,
31 pub clear_color: metal::MTLClearColor,
32 pub viewport: metal::MTLViewport,
33}
34
35/// State that needs to be preserved across render pass breaks
36#[derive(Clone, Debug)]
37pub struct RenderState {
38 pub viewport: metal::MTLViewport,
39 pub blend_mode: Option<BlendMode>,
40 // Add other state that needs to be preserved
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub enum BlendMode {
45 Normal,
46 Multiply,
47 Screen,
48 // Add other blend modes as needed
49}
50
51/// Context provided to Metal render callbacks
52pub struct MetalRenderContext<'a> {
53 pub command_buffer: &'a CommandBufferRef,
54 pub drawable_texture: &'a metal::TextureRef,
55 pub viewport_size: Size<DevicePixels>,
56 pub device: &'a Device,
57 pub bounds: crate::Bounds<ScaledPixels>,
58 pub scale_factor: f32,
59}
60
61/// Manages the rendering pipeline with support for render pass breaks
62pub struct RenderPassManager {
63 device: Device,
64 command_queue: CommandQueue,
65 current_state: RenderState,
66}
67
68impl RenderPassManager {
69 pub fn new(device: Device, command_queue: CommandQueue) -> Self {
70 Self {
71 device,
72 command_queue,
73 current_state: RenderState {
74 viewport: metal::MTLViewport {
75 originX: 0.0,
76 originY: 0.0,
77 width: 0.0,
78 height: 0.0,
79 znear: 0.0,
80 zfar: 1.0,
81 },
82 blend_mode: None,
83 },
84 }
85 }
86
87 /// Convert a scene into a list of render commands
88 pub fn build_render_commands<'a>(
89 &self,
90 scene: &'a Scene,
91 drawable_texture: &metal::TextureRef,
92 viewport_size: Size<DevicePixels>,
93 is_opaque: bool,
94 ) -> Vec<RenderCommand<'a>> {
95 let mut commands = Vec::new();
96
97 // Initial render pass configuration
98 let alpha = if is_opaque { 1.0 } else { 0.0 };
99 let descriptor = RenderPassDescriptor {
100 texture: drawable_texture.to_owned(),
101 load_action: MTLLoadAction::Clear,
102 store_action: MTLStoreAction::Store,
103 clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, alpha),
104 viewport: metal::MTLViewport {
105 originX: 0.0,
106 originY: 0.0,
107 width: i32::from(viewport_size.width) as f64,
108 height: i32::from(viewport_size.height) as f64,
109 znear: 0.0,
110 zfar: 1.0,
111 },
112 };
113
114 commands.push(RenderCommand::BeginRenderPass { descriptor });
115
116 // Process batches, inserting render pass breaks for MetalViews
117 let mut in_render_pass = true;
118
119 for batch in scene.batches() {
120 match batch {
121 #[cfg(target_os = "macos")]
122 PrimitiveBatch::MetalViews(metal_views) => {
123 // End current render pass
124 if in_render_pass {
125 commands.push(RenderCommand::EndRenderPass);
126 in_render_pass = false;
127 }
128
129 // Add commands for each MetalView
130 for metal_view in metal_views {
131 commands.push(RenderCommand::ExecuteMetalCallback {
132 metal_view,
133 viewport_size,
134 });
135 }
136 }
137 _ => {
138 // Ensure we're in a render pass
139 if !in_render_pass {
140 let descriptor = RenderPassDescriptor {
141 texture: drawable_texture.to_owned(),
142 load_action: MTLLoadAction::Load, // Load existing content
143 store_action: MTLStoreAction::Store,
144 clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, 0.0),
145 viewport: self.current_state.viewport,
146 };
147 commands.push(RenderCommand::BeginRenderPass { descriptor });
148 in_render_pass = true;
149 }
150
151 // Add primitive drawing command
152 commands.push(RenderCommand::DrawPrimitives {
153 batch,
154 viewport_size,
155 });
156 }
157 }
158 }
159
160 // Ensure we end the final render pass
161 if in_render_pass {
162 commands.push(RenderCommand::EndRenderPass);
163 }
164
165 commands
166 }
167
168 /// Execute a list of render commands
169 pub fn execute_commands<F>(
170 &mut self,
171 commands: &[RenderCommand],
172 command_buffer: &CommandBufferRef,
173 drawable_texture: &metal::TextureRef,
174 mut draw_primitives: F,
175 ) -> Result<(), anyhow::Error>
176 where
177 F: FnMut(
178 PrimitiveBatch,
179 &RenderCommandEncoderRef,
180 Size<DevicePixels>,
181 ) -> Result<(), anyhow::Error>,
182 {
183 let mut current_encoder: Option<metal::RenderCommandEncoder> = None;
184
185 for command in commands {
186 match command {
187 RenderCommand::BeginRenderPass { descriptor } => {
188 // End any existing encoder
189 if let Some(encoder) = current_encoder.take() {
190 encoder.end_encoding();
191 }
192
193 // Create new render pass
194 let render_pass_descriptor = metal::RenderPassDescriptor::new();
195 let color_attachment = render_pass_descriptor
196 .color_attachments()
197 .object_at(0)
198 .unwrap();
199
200 color_attachment.set_texture(Some(&descriptor.texture));
201 color_attachment.set_load_action(descriptor.load_action);
202 color_attachment.set_store_action(descriptor.store_action);
203 color_attachment.set_clear_color(descriptor.clear_color);
204
205 let encoder =
206 command_buffer.new_render_command_encoder(&render_pass_descriptor);
207 encoder.set_viewport(descriptor.viewport);
208 self.current_state.viewport = descriptor.viewport;
209
210 current_encoder = Some(encoder);
211 }
212
213 RenderCommand::DrawPrimitives {
214 batch,
215 viewport_size,
216 } => {
217 if let Some(ref encoder) = current_encoder {
218 draw_primitives(*batch, encoder, *viewport_size)?;
219 }
220 }
221
222 RenderCommand::ExecuteMetalCallback {
223 metal_view,
224 viewport_size,
225 } => {
226 // End current encoder if any
227 if let Some(encoder) = current_encoder.take() {
228 encoder.end_encoding();
229 }
230
231 // Create context for the callback
232 let context = MetalRenderContext {
233 command_buffer,
234 drawable_texture,
235 viewport_size: *viewport_size,
236 device: &self.device,
237 bounds: metal_view.bounds.clone(),
238 scale_factor: 2.0, // TODO: Get actual scale factor
239 };
240
241 // Create a new render command encoder for the callback
242 let render_pass_descriptor = metal::RenderPassDescriptor::new();
243 let color_attachment = render_pass_descriptor
244 .color_attachments()
245 .object_at(0)
246 .unwrap();
247
248 color_attachment.set_texture(Some(drawable_texture));
249 color_attachment.set_load_action(MTLLoadAction::Load);
250 color_attachment.set_store_action(MTLStoreAction::Store);
251
252 let encoder =
253 command_buffer.new_render_command_encoder(&render_pass_descriptor);
254
255 // Invoke the callback
256 (metal_view.render_callback)(
257 &encoder,
258 drawable_texture,
259 context.bounds.into(),
260 context.scale_factor,
261 );
262
263 encoder.end_encoding();
264 }
265
266 RenderCommand::EndRenderPass => {
267 if let Some(encoder) = current_encoder.take() {
268 encoder.end_encoding();
269 }
270 }
271 }
272 }
273
274 // Ensure any remaining encoder is ended
275 if let Some(encoder) = current_encoder {
276 encoder.end_encoding();
277 }
278
279 Ok(())
280 }
281
282 /// Save the current render state
283 pub fn save_state(&self) -> RenderState {
284 self.current_state.clone()
285 }
286
287 /// Restore a previously saved render state
288 pub fn restore_state(&mut self, state: RenderState) {
289 self.current_state = state;
290 }
291}
292
293/// Builder for constructing render command lists
294pub struct RenderCommandBuilder<'a> {
295 commands: Vec<RenderCommand<'a>>,
296}
297
298impl<'a> RenderCommandBuilder<'a> {
299 pub fn new() -> Self {
300 Self {
301 commands: Vec::new(),
302 }
303 }
304
305 pub fn begin_render_pass(mut self, descriptor: RenderPassDescriptor) -> Self {
306 self.commands
307 .push(RenderCommand::BeginRenderPass { descriptor });
308 self
309 }
310
311 pub fn draw_primitives(
312 mut self,
313 batch: PrimitiveBatch<'a>,
314 viewport_size: Size<DevicePixels>,
315 ) -> Self {
316 self.commands.push(RenderCommand::DrawPrimitives {
317 batch,
318 viewport_size,
319 });
320 self
321 }
322
323 pub fn execute_metal_callback(
324 mut self,
325 metal_view: &'a PaintMetalView,
326 viewport_size: Size<DevicePixels>,
327 ) -> Self {
328 self.commands.push(RenderCommand::ExecuteMetalCallback {
329 metal_view,
330 viewport_size,
331 });
332 self
333 }
334
335 pub fn end_render_pass(mut self) -> Self {
336 self.commands.push(RenderCommand::EndRenderPass);
337 self
338 }
339
340 pub fn build(self) -> Vec<RenderCommand<'a>> {
341 self.commands
342 }
343}