1#[cfg(any(
2 target_os = "linux",
3 target_os = "macos",
4 target_os = "ios",
5 target_os = "windows"
6))]
7use crate::GpuCanvasContext;
8use crate::{
9 App, Bounds, Element, ElementId, GlobalElementId, IntoElement, LayoutId, ObjectFit, Pixels,
10 Style, StyleRefinement, Styled, Window,
11};
12#[cfg(target_os = "windows")]
16use gpui_engine::DirectXSource;
17use gpui_engine::SurfaceSource;
18#[cfg(any(target_os = "macos", target_os = "ios"))]
19use objc2_core_video::{CVPixelBufferGetHeight, CVPixelBufferGetWidth};
20use refineable::Refineable;
21
22pub struct Surface {
29 source: SurfaceSource,
30 object_fit: ObjectFit,
31 style: StyleRefinement,
32}
33
34pub fn surface(source: impl Into<SurfaceSource>) -> Surface {
69 Surface {
70 source: source.into(),
71 object_fit: ObjectFit::Contain,
72 style: Default::default(),
73 }
74}
75
76impl Surface {
77 pub fn object_fit(mut self, object_fit: ObjectFit) -> Self {
86 self.object_fit = object_fit;
87 self
88 }
89}
90
91impl Element for Surface {
92 type RequestLayoutState = ();
93 type PrepaintState = ();
94
95 fn id(&self) -> Option<ElementId> {
96 None
97 }
98
99 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
100 None
101 }
102
103 fn request_layout(
104 &mut self,
105 _global_id: Option<&GlobalElementId>,
106 window: &mut Window,
107 cx: &mut App,
108 ) -> (LayoutId, Self::RequestLayoutState) {
109 let mut style = Style::default();
110 style.refine(&self.style);
111 let layout_id = window.request_layout(style, [], cx);
112 (layout_id, ())
113 }
114
115 fn prepaint(
116 &mut self,
117 _global_id: Option<&GlobalElementId>,
118 _bounds: Bounds<Pixels>,
119 _request_layout: &mut Self::RequestLayoutState,
120 _window: &mut Window,
121 _cx: &mut App,
122 ) -> Self::PrepaintState {
123 }
124
125 fn paint(
126 &mut self,
127 _global_id: Option<&GlobalElementId>,
128 #[cfg_attr(
129 not(any(target_os = "macos", target_os = "ios", target_os = "windows")),
130 allow(unused_variables)
131 )]
132 bounds: Bounds<Pixels>,
133 _: &mut Self::RequestLayoutState,
134 _: &mut Self::PrepaintState,
135 #[cfg_attr(
136 not(any(target_os = "macos", target_os = "ios", target_os = "windows")),
137 allow(unused_variables)
138 )]
139 window: &mut Window,
140 #[cfg_attr(
141 not(any(target_os = "macos", target_os = "ios", target_os = "windows")),
142 allow(unused_variables)
143 )]
144 cx: &mut App,
145 ) {
146 match &self.source {
147 #[cfg(any(target_os = "macos", target_os = "ios"))]
148 SurfaceSource::CoreVideo(image_buffer) => {
149 let size = crate::size(
150 CVPixelBufferGetWidth(image_buffer).into(),
151 CVPixelBufferGetHeight(image_buffer).into(),
152 );
153 let new_bounds = self.object_fit.get_bounds(bounds, size);
154 let mut ctx = GpuCanvasContext::new(window, cx, new_bounds);
156 ctx.paint_surface(image_buffer.clone());
157 }
158 #[cfg(target_os = "windows")]
159 SurfaceSource::DirectX(source) => {
160 let new_bounds = match directx_source_size(source) {
161 Some(size) => self.object_fit.get_bounds(bounds, size),
162 None => bounds,
163 };
164 let mut ctx = GpuCanvasContext::new(window, cx, new_bounds);
166 ctx.paint_surface(SurfaceSource::DirectX(source.clone()));
167 }
168 #[cfg(target_os = "linux")]
169 SurfaceSource::DmaBuf(handle) => {
170 let size = crate::size(
171 crate::DevicePixels::from(handle.width as i32),
172 crate::DevicePixels::from(handle.height as i32),
173 );
174 let new_bounds = self.object_fit.get_bounds(bounds, size);
175 let mut ctx = GpuCanvasContext::new(window, cx, new_bounds);
177 ctx.paint_surface(handle.clone());
178 }
179 #[allow(unreachable_patterns)]
180 _ => {}
181 }
182 }
183}
184
185impl IntoElement for Surface {
186 type Element = Self;
187
188 fn into_element(self) -> Self::Element {
189 self
190 }
191}
192
193impl Styled for Surface {
194 fn style(&mut self) -> &mut StyleRefinement {
195 &mut self.style
196 }
197}
198
199#[cfg(target_os = "windows")]
206fn directx_source_size(source: &DirectXSource) -> Option<crate::Size<crate::DevicePixels>> {
207 use windows::Win32::Graphics::Direct3D11::{D3D11_TEXTURE2D_DESC, ID3D11Texture2D};
208 use windows::core::Interface as _;
209
210 unsafe {
211 let mut desc = D3D11_TEXTURE2D_DESC::default();
212 match source {
213 DirectXSource::Texture(texture) => texture.GetDesc(&mut desc),
214 DirectXSource::View(view) => {
215 let resource = view.GetResource().ok()?;
216 let texture: ID3D11Texture2D = resource.cast().ok()?;
217 texture.GetDesc(&mut desc);
218 }
219 DirectXSource::Shared(shared) => {
221 return Some(crate::size(
222 crate::DevicePixels::from(shared.width as i32),
223 crate::DevicePixels::from(shared.height as i32),
224 ));
225 }
226 }
227 Some(crate::size(
228 crate::DevicePixels::from(desc.Width as i32),
229 crate::DevicePixels::from(desc.Height as i32),
230 ))
231 }
232}
233
234#[cfg(all(test, target_os = "linux"))]
235mod tests {
236 use super::*;
237 use crate::{Context, Render, TestAppContext, Window};
238 use gpui_engine::{DmaBufHandle, DmaBufPlane, SurfaceFormatKind, SurfaceSource};
239 use std::os::fd::OwnedFd;
240
241 struct SurfaceView {
242 handle: DmaBufHandle,
243 }
244
245 impl Render for SurfaceView {
246 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
247 surface(self.handle.clone())
248 }
249 }
250
251 fn descriptor() -> OwnedFd {
253 std::fs::File::open("/dev/null").expect("/dev/null").into()
254 }
255
256 #[gpui::test]
259 fn a_surface_handle_reaches_the_scene(cx: &mut TestAppContext) {
260 let handle = DmaBufHandle::new(
261 4,
262 4,
263 SurfaceFormatKind::rgba8(),
264 DmaBufHandle::LINEAR,
265 [DmaBufPlane::new(descriptor(), 0, 16)],
266 None,
267 );
268
269 let expected = handle.clone();
270 let (_view, cx) = cx.add_window_view(|_, _| SurfaceView { handle });
273 cx.run_until_parked();
274
275 let surfaces = cx.update(|window, _cx| window.painted_surfaces());
276 assert_eq!(
277 surfaces.len(),
278 1,
279 "the surface should be in the scene exactly once"
280 );
281 assert_eq!(surfaces[0].source, SurfaceSource::DmaBuf(expected));
282 }
283}