orinium_browser/platform/renderer/
gpu.rs

1//! wgpuを使用してGPUで描画するためのコンテキストと処理を提供するモジュール
2
3use crate::engine::renderer_model::DrawCommand;
4use anyhow::Result;
5use std::sync::Arc;
6use std::{env, fmt::Debug};
7use wgpu::util::DeviceExt;
8use winit::window::Window;
9
10use super::glyph::text::{TextRenderer, TextSection};
11
12/// GPU描画コンテキスト
13pub struct GpuRenderer {
14    /// GPUの描画対象
15    surface: wgpu::Surface<'static>,
16    /// GPUの論理デバイス
17    device: wgpu::Device,
18    /// コマンド送信用キュー
19    queue: wgpu::Queue,
20    /// サーフェス設定、解像度・フォーマットなどのフレームバッファ設定
21    config: wgpu::SurfaceConfiguration,
22    /// WindowSize
23    size: winit::dpi::PhysicalSize<u32>,
24    /// ディスプレイ倍率
25    scale_factor: f64,
26    /// RenderPipelin(頂点 to ピクセル)
27    render_pipeline: wgpu::RenderPipeline,
28    /// 頂点バッファ
29    vertex_buffer: Option<wgpu::Buffer>,
30    /// 頂点
31    vertices: Vec<Vertex>,
32    /// 頂点数
33    num_vertices: u32,
34
35    /// テキスト描画用ラッパー
36    text_renderer: Option<TextRenderer>,
37
38    /// テキストカリングを有効にする
39    enable_text_culling: bool,
40}
41
42#[repr(C)]
43#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
44struct Vertex {
45    position: [f32; 3],
46    color: [f32; 4],
47}
48
49impl Vertex {
50    fn desc() -> wgpu::VertexBufferLayout<'static> {
51        wgpu::VertexBufferLayout {
52            array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
53            step_mode: wgpu::VertexStepMode::Vertex,
54            attributes: &[
55                wgpu::VertexAttribute {
56                    offset: 0,
57                    shader_location: 0,
58                    format: wgpu::VertexFormat::Float32x3,
59                },
60                wgpu::VertexAttribute {
61                    offset: size_of::<[f32; 3]>() as wgpu::BufferAddress,
62                    shader_location: 1,
63                    format: wgpu::VertexFormat::Float32x4,
64                },
65            ],
66        }
67    }
68}
69
70impl GpuRenderer {
71    /// 新しいGPUレンダラーを作成
72    pub async fn new(window: Arc<Window>, font_path: Option<&str>) -> Result<Self> {
73        let size = window.inner_size();
74        let scale_factor = window.scale_factor();
75
76        // GPUドライバとの通信インスタンス
77        // wgpuインスタンスの作成
78        //
79        // [`InstanceDescriptor::new_with_out_display_hundler`] の実装を参考に
80        // backends 選択は [`select_wgpu_backends`] を使った実装。
81        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
82            backends: select_wgpu_backends(),
83            flags: Default::default(),
84            memory_budget_thresholds: Default::default(),
85            backend_options: Default::default(),
86            display: None,
87        });
88
89        // OSウィンドウとGPUの描画対象(サーフェス)を関連付ける
90        // サーフェスの作成
91        let surface = instance.create_surface(window.clone())?;
92
93        // 利用可能なGPU(物理デバイス)アダプターの取得
94        let adapter = instance
95            .request_adapter(&wgpu::RequestAdapterOptions {
96                power_preference: wgpu::PowerPreference::default(),
97                compatible_surface: Some(&surface),
98                force_fallback_adapter: false,
99            })
100            .await?;
101
102        // デバイスとキューの作成
103        let (device, queue) = adapter
104            .request_device(&wgpu::DeviceDescriptor {
105                label: None,
106                required_features: wgpu::Features::empty(),
107                required_limits: wgpu::Limits::default(),
108                experimental_features: Default::default(),
109                memory_hints: wgpu::MemoryHints::default(),
110                trace: Default::default(),
111            })
112            .await?;
113
114        // サーフェス設定
115        // フレームバッファ設定(解像度・フォーマットなど)
116        let surface_caps = surface.get_capabilities(&adapter);
117        let surface_format = surface_caps
118            .formats
119            .iter()
120            .copied()
121            .find(|f| f.is_srgb())
122            .unwrap_or(surface_caps.formats[0]);
123
124        let config = wgpu::SurfaceConfiguration {
125            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
126            format: surface_format,
127            width: size.width,
128            height: size.height,
129            present_mode: surface_caps.present_modes[0],
130            alpha_mode: surface_caps.alpha_modes[0],
131            view_formats: vec![],
132            desired_maximum_frame_latency: 2,
133        };
134        surface.configure(&device, &config);
135
136        // シェーダーの読み込み
137        // シェーダーモジュールの作成
138        // vertex/fragment for main pipeline
139        let main_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
140            label: Some("Main Shader"),
141            source: wgpu::ShaderSource::Wgsl(include_str!("shader/main.wgsl").into()),
142        });
143
144        // --- レンダーパイプライン(頂点→ピクセル変換のルール)の作成 ---
145        let render_pipeline_layout =
146            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
147                label: Some("Render Pipeline Layout"),
148                bind_group_layouts: &[],
149                immediate_size: 0,
150            });
151
152        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
153            label: Some("Render Pipeline"),
154            layout: Some(&render_pipeline_layout),
155            cache: None,
156            vertex: wgpu::VertexState {
157                module: &main_shader,
158                entry_point: Some("vs_main"),
159                buffers: &[Vertex::desc()],
160                compilation_options: wgpu::PipelineCompilationOptions::default(),
161            },
162            fragment: Some(wgpu::FragmentState {
163                module: &main_shader,
164                entry_point: Some("fs_main"),
165                targets: &[Some(wgpu::ColorTargetState {
166                    format: config.format,
167                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
168                    write_mask: wgpu::ColorWrites::ALL,
169                })],
170                compilation_options: wgpu::PipelineCompilationOptions::default(),
171            }),
172            primitive: wgpu::PrimitiveState {
173                topology: wgpu::PrimitiveTopology::TriangleList,
174                strip_index_format: None,
175                front_face: wgpu::FrontFace::Ccw,
176                cull_mode: None, // 三角扇がカリングで消えちゃう...
177                polygon_mode: wgpu::PolygonMode::Fill,
178                unclipped_depth: false,
179                conservative: false,
180            },
181            depth_stencil: None,
182            multisample: wgpu::MultisampleState {
183                count: 1,
184                mask: !0,
185                alpha_to_coverage_enabled: false,
186            },
187            multiview_mask: None,
188        });
189        // --- レンダーパイプライン作成終了 ---
190
191        // テキスト描画用ラッパーの初期化。引数で渡されたフォントパスがあればそれを優先して読み込む。
192        let text_renderer = if let Some(p) = font_path {
193            match std::fs::read(p) {
194                Ok(bytes) => {
195                    match TextRenderer::new_from_bytes(&device, &queue, config.format, bytes) {
196                        Ok(t) => Some(t),
197                        Err(e) => {
198                            log::warn!(target:"PRender::gpu::font" ,"failed to init text renderer from provided font: {}", e);
199                            None
200                        }
201                    }
202                }
203                Err(e) => {
204                    log::warn!(target:"PRender::gpu::font" ,"failed to read font path '{}': {}", p, e);
205                    None
206                }
207            }
208        } else {
209            match TextRenderer::new_from_device(&device, &queue, config.format) {
210                Ok(t) => Some(t),
211                Err(e) => {
212                    log::warn!(target:"PRender::gpu::font" ,"no system font found for text renderer: {}", e);
213                    None
214                }
215            }
216        };
217
218        // Enable text culling by default, allow override by env var
219        let enable_text_culling = std::env::var("ORINIUM_TEXT_CULL")
220            .map(|v| v != "0")
221            .unwrap_or(true);
222
223        Ok(Self {
224            surface,
225            device,
226            queue,
227            config,
228            size,
229            scale_factor,
230            render_pipeline,
231            vertex_buffer: None,
232            vertices: vec![],
233            num_vertices: 0,
234            text_renderer,
235            enable_text_culling,
236        })
237    }
238
239    /// ウィンドウサイズが変更された時の処理
240    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
241        if new_size.width > 0 && new_size.height > 0 {
242            log::info!(target:"PRender::gpu::resized", "Resized: {}x{}", new_size.width, new_size.height);
243
244            let old_size = self.size;
245
246            self.size = new_size;
247
248            self.config.width = new_size.width;
249            self.config.height = new_size.height;
250
251            self.surface.configure(&self.device, &self.config);
252
253            self.update_vertices(old_size, new_size);
254
255            if let Some(tr) = &mut self.text_renderer {
256                tr.resize_view(
257                    self.config.width as f32,
258                    self.config.height as f32,
259                    &self.queue,
260                );
261            }
262        }
263    }
264
265    /// 描画命令を解析して頂点バッファやテキストキューに登録
266    pub fn parse_draw_commands(&mut self, commands: &[DrawCommand]) {
267        let screen_width = self.size.width as f32;
268        let screen_height = self.size.height as f32;
269
270        // --- 頂点データ ---
271        let mut vertices = Vec::new();
272        // --- Text ---
273        let mut sections: Vec<TextSection> = Vec::new();
274        // --- scale_factor ---
275        let sf = self.scale_factor as f32;
276        // --- transform stack ---
277        let mut transform_stack: Vec<(f32, f32)> = vec![(0.0, 0.0)];
278        let current_transform = |stack: &Vec<(f32, f32)>| -> (f32, f32) {
279            let mut dx = 0.0;
280            let mut dy = 0.0;
281            for (x, y) in stack.iter() {
282                dx += x;
283                dy += y;
284            }
285            (dx, dy)
286        };
287        // --- clip stack ---
288        #[derive(Clone, Copy)]
289        struct ClipRect {
290            x: f32,
291            y: f32,
292            w: f32,
293            h: f32,
294        }
295        let mut clip_stack: Vec<ClipRect> = vec![ClipRect {
296            x: 0.0,
297            y: 0.0,
298            w: screen_width,
299            h: screen_height,
300        }];
301        let current_clip = |stack: &Vec<ClipRect>| -> ClipRect { *stack.last().unwrap() };
302
303        for command in commands {
304            match command {
305                // Transform (Push / Pop)
306                DrawCommand::PushTransform { dx, dy } => {
307                    transform_stack.push((*dx, *dy));
308                }
309                DrawCommand::PopTransform => {
310                    if transform_stack.len() > 1 {
311                        transform_stack.pop();
312                    }
313                }
314
315                // Clip (Push / Pop)
316                DrawCommand::PushClip {
317                    x,
318                    y,
319                    width: w,
320                    height: h,
321                } => {
322                    let (tdx, tdy) = current_transform(&transform_stack);
323                    let new_clip = ClipRect {
324                        x: x + tdx,
325                        y: y + tdy,
326                        w: *w,
327                        h: *h,
328                    };
329
330                    // 現在の clip との AND を取る
331                    let parent = current_clip(&clip_stack);
332
333                    let x1 = new_clip.x.max(parent.x);
334                    let y1 = new_clip.y.max(parent.y);
335                    let x2 = (new_clip.x + new_clip.w).min(parent.x + parent.w);
336                    let y2 = (new_clip.y + new_clip.h).min(parent.y + parent.h);
337
338                    clip_stack.push(ClipRect {
339                        x: x1,
340                        y: y1,
341                        w: (x2 - x1).max(0.0),
342                        h: (y2 - y1).max(0.0),
343                    });
344                }
345
346                DrawCommand::PopClip => {
347                    if clip_stack.len() > 1 {
348                        clip_stack.pop();
349                    }
350                }
351
352                // Rectangle
353                DrawCommand::DrawRect {
354                    x,
355                    y,
356                    width: w,
357                    height: h,
358                    color,
359                } => {
360                    // transform
361                    let (tdx, tdy) = current_transform(&transform_stack);
362                    let mut x1 = (x + tdx) * sf;
363                    let mut y1 = (y + tdy) * sf;
364                    let mut x2 = x1 + w * sf;
365                    let mut y2 = y1 + h * sf;
366
367                    // clip 取得
368                    let clip = current_clip(&clip_stack);
369
370                    // 完全に外なら skip
371                    if x2 <= clip.x * sf
372                        || x1 >= (clip.x + clip.w) * sf
373                        || y2 <= clip.y * sf
374                        || y1 >= (clip.y + clip.h) * sf
375                    {
376                        continue;
377                    }
378
379                    // 部分クリップ
380                    x1 = x1.max(clip.x * sf);
381                    y1 = y1.max(clip.y * sf);
382                    x2 = x2.min((clip.x + clip.w) * sf);
383                    y2 = y2.min((clip.y + clip.h) * sf);
384
385                    // NDC
386                    let ndc = |v, max| (v / max) * 2.0 - 1.0;
387
388                    let px1 = ndc(x1, screen_width);
389                    let py1 = -ndc(y1, screen_height);
390                    let px2 = ndc(x2, screen_width);
391                    let py2 = -ndc(y2, screen_height);
392
393                    let color = color.to_linear_f32_array();
394
395                    #[rustfmt::skip]
396                    vertices.extend_from_slice(&[
397                        Vertex { position: [px1, py1, 0.0], color },
398                        Vertex { position: [px1, py2, 0.0], color },
399                        Vertex { position: [px2, py1, 0.0], color },
400
401                        Vertex { position: [px2, py1, 0.0], color },
402                        Vertex { position: [px1, py2, 0.0], color },
403                        Vertex { position: [px2, py2, 0.0], color },
404                    ]);
405                }
406
407                // Text
408                DrawCommand::DrawText { x, y, text, style } => {
409                    let (tdx, tdy) = current_transform(&transform_stack);
410
411                    let clip = current_clip(&clip_stack);
412
413                    let tw = clip.w;
414                    let th = clip.h;
415
416                    let font_size = &style.font_size;
417
418                    // Text culling: if enabled and the text's bounding box is fully outside current clip, skip creating buffer
419                    let mut skip_text = false;
420                    if self.enable_text_culling {
421                        // compute screen-space bbox
422                        let sx1 = (x + tdx) * sf;
423                        let sy1 = (y + tdy) * sf;
424                        // if width/height are zero or NaN, estimate from font size and line count
425                        let est_w = if !tw.is_finite() || tw <= 0.0 {
426                            // fall back: estimate width as font_size * 10.0 * approximate_chars
427                            (*font_size * sf) * (text.len().max(1) as f32) * 0.5
428                        } else {
429                            tw * sf
430                        };
431                        let est_h = if !th.is_finite() || th <= 0.0 {
432                            // estimate height as font_size * 1.2 * lines
433                            (*font_size * sf) * 1.2 * (text.lines().count() as f32).max(1.0)
434                        } else {
435                            th * sf
436                        };
437                        let sx2 = sx1 + est_w;
438                        let sy2 = sy1 + est_h;
439
440                        let clip_l = clip.x * sf;
441                        let clip_t = clip.y * sf;
442                        let clip_r = (clip.x + clip.w) * sf;
443                        let clip_b = (clip.y + clip.h) * sf;
444
445                        if sx2 <= clip_l || sx1 >= clip_r || sy2 <= clip_t || sy1 >= clip_b {
446                            skip_text = true;
447                        }
448                    }
449
450                    if skip_text {
451                        continue;
452                    }
453
454                    // Use TextRenderer helper to create a Buffer with correct FontSystem handling
455                    let section = if let Some(tr) = &mut self.text_renderer {
456                        let mut render_text_style = *style;
457                        render_text_style.font_size = *font_size * sf;
458                        let buffer = tr.create_buffer_for_text(text, render_text_style);
459
460                        TextSection {
461                            screen_position: ((*x + tdx) * sf, (*y + tdy) * sf),
462                            clip_origin: (clip.x * sf, clip.y * sf),
463                            bounds: (tw * sf, th * sf),
464                            buffer,
465                        }
466                    } else {
467                        // No text renderer available; skip
468                        continue;
469                    };
470                    sections.push(section);
471                }
472
473                // Polygon
474                DrawCommand::DrawPolygon { points, color } => {
475                    // transform
476                    let (tdx, tdy) = current_transform(&transform_stack);
477                    let transformed_points: Vec<(f32, f32)> = points
478                        .iter()
479                        .map(|(px, py)| ((px + tdx) * sf, (py + tdy) * sf))
480                        .collect();
481
482                    // clip 取得
483                    let clip = current_clip(&clip_stack);
484                    // clip in scaled (screen) coords
485                    let clip_l = clip.x * sf;
486                    let clip_t = clip.y * sf;
487                    let clip_r = (clip.x + clip.w) * sf;
488                    let clip_b = (clip.y + clip.h) * sf;
489
490                    // Quick reject by bounding box
491                    let mut min_x = f32::INFINITY;
492                    let mut min_y = f32::INFINITY;
493                    let mut max_x = f32::NEG_INFINITY;
494                    let mut max_y = f32::NEG_INFINITY;
495                    for (x, y) in transformed_points.iter() {
496                        min_x = min_x.min(*x);
497                        min_y = min_y.min(*y);
498                        max_x = max_x.max(*x);
499                        max_y = max_y.max(*y);
500                    }
501                    if max_x <= clip_l || min_x >= clip_r || max_y <= clip_t || min_y >= clip_b {
502                        // fully outside
503                        continue;
504                    }
505
506                    // Helper: Sutherland–Hodgman polygon clipping against an axis-aligned edge
507                    let clip_against_edge = |poly: &Vec<(f32, f32)>, edge: u8| -> Vec<(f32, f32)> {
508                        // edge: 0=left,1=right,2=top,3=bottom
509                        let mut out: Vec<(f32, f32)> = Vec::new();
510                        if poly.is_empty() {
511                            return out;
512                        }
513                        let len = poly.len();
514                        for i in 0..len {
515                            let (sx, sy) = poly[i];
516                            let (ex, ey) = poly[(i + 1) % len];
517                            // inside test
518                            let inside = |x: f32, y: f32| -> bool {
519                                match edge {
520                                    0 => x >= clip_l, // left
521                                    1 => x <= clip_r, // right
522                                    2 => y >= clip_t, // top
523                                    3 => y <= clip_b, // bottom
524                                    _ => true,
525                                }
526                            };
527                            let s_in = inside(sx, sy);
528                            let e_in = inside(ex, ey);
529
530                            if s_in && e_in {
531                                // both inside
532                                out.push((ex, ey));
533                            } else if s_in && !e_in {
534                                // going out: add intersection
535                                // compute intersection between segment and clipping line
536                                let (ix, iy) = match edge {
537                                    0 | 1 => {
538                                        // vertical line x = clip_l or clip_r
539                                        let x_edge = if edge == 0 { clip_l } else { clip_r };
540                                        let dx = ex - sx;
541                                        if dx.abs() < f32::EPSILON {
542                                            (x_edge, sy)
543                                        } else {
544                                            let t = (x_edge - sx) / dx;
545                                            (x_edge, sy + t * (ey - sy))
546                                        }
547                                    }
548                                    2 | 3 => {
549                                        // horizontal line y = clip_t or clip_b
550                                        let y_edge = if edge == 2 { clip_t } else { clip_b };
551                                        let dy = ey - sy;
552                                        if dy.abs() < f32::EPSILON {
553                                            (sx, y_edge)
554                                        } else {
555                                            let t = (y_edge - sy) / dy;
556                                            (sx + t * (ex - sx), y_edge)
557                                        }
558                                    }
559                                    _ => (ex, ey),
560                                };
561                                out.push((ix, iy));
562                            } else if !s_in && e_in {
563                                // entering: add intersection then end point
564                                let (ix, iy) = match edge {
565                                    0 | 1 => {
566                                        let x_edge = if edge == 0 { clip_l } else { clip_r };
567                                        let dx = ex - sx;
568                                        if dx.abs() < f32::EPSILON {
569                                            (x_edge, sy)
570                                        } else {
571                                            let t = (x_edge - sx) / dx;
572                                            (x_edge, sy + t * (ey - sy))
573                                        }
574                                    }
575                                    2 | 3 => {
576                                        let y_edge = if edge == 2 { clip_t } else { clip_b };
577                                        let dy = ey - sy;
578                                        if dy.abs() < f32::EPSILON {
579                                            (sx, y_edge)
580                                        } else {
581                                            let t = (y_edge - sy) / dy;
582                                            (sx + t * (ex - sx), y_edge)
583                                        }
584                                    }
585                                    _ => (ex, ey),
586                                };
587                                out.push((ix, iy));
588                                out.push((ex, ey));
589                            } else {
590                                // both outside: do nothing
591                            }
592                        }
593                        out
594                    };
595
596                    // Triangulate polygon into fan triangles from vertex 0, clip each triangle, and push resulting triangles
597                    if transformed_points.len() < 3 {
598                        continue;
599                    }
600
601                    // NDC helper
602                    let ndc = |v: f32, max: f32| (v / max) * 2.0 - 1.0;
603
604                    let color_arr = color.to_linear_f32_array();
605
606                    let v0 = transformed_points[0];
607                    for i in 1..(transformed_points.len() - 1) {
608                        let tri = vec![v0, transformed_points[i], transformed_points[i + 1]];
609                        // clip triangle against rect using Sutherland–Hodgman (4 edges)
610                        let mut poly = tri;
611                        poly = clip_against_edge(&poly, 0); // left
612                        if poly.is_empty() {
613                            continue;
614                        }
615                        poly = clip_against_edge(&poly, 1); // right
616                        if poly.is_empty() {
617                            continue;
618                        }
619                        poly = clip_against_edge(&poly, 2); // top
620                        if poly.is_empty() {
621                            continue;
622                        }
623                        poly = clip_against_edge(&poly, 3); // bottom
624                        if poly.is_empty() {
625                            continue;
626                        }
627
628                        // triangulate resulting polygon as fan
629                        for j in 1..(poly.len() - 1) {
630                            let p1 = poly[0];
631                            let p2 = poly[j];
632                            let p3 = poly[j + 1];
633
634                            let px1 = ndc(p1.0, screen_width);
635                            let py1 = -ndc(p1.1, screen_height);
636                            let px2 = ndc(p2.0, screen_width);
637                            let py2 = -ndc(p2.1, screen_height);
638                            let px3 = ndc(p3.0, screen_width);
639                            let py3 = -ndc(p3.1, screen_height);
640
641                            vertices.push(Vertex {
642                                position: [px1, py1, 0.0],
643                                color: color_arr,
644                            });
645                            vertices.push(Vertex {
646                                position: [px2, py2, 0.0],
647                                color: color_arr,
648                            });
649                            vertices.push(Vertex {
650                                position: [px3, py3, 0.0],
651                                color: color_arr,
652                            });
653                        }
654                    }
655                }
656
657                // Ellipse
658                #[allow(unused)]
659                DrawCommand::DrawEllipse {
660                    center,
661                    radius_x,
662                    radius_y,
663                    color,
664                } => {
665                    // transform
666                    let (tdx, tdy) = current_transform(&transform_stack);
667                    let cx = center.0 + tdx;
668                    let cy = center.1 + tdy;
669
670                    // clip 取得
671                    let clip = current_clip(&clip_stack);
672
673                    todo!("Ellipse drawing with clipping is not implemented yet");
674                }
675            }
676        }
677
678        self.set_vertex_buffer(vertices);
679
680        // テキストセクションをキューに追加
681        if let Some(tr) = &mut self.text_renderer {
682            tr.queue(&self.device, &self.queue, &sections).unwrap();
683        }
684    }
685
686    /// フレームを描画
687    ///
688    /// TODO:
689    /// [`wgpu::CurrentSurfaceTexture`] をよりよく処理する必要があります。
690    /// 現在は、 Success 時以外の結果を無視し、Errorにまとめて返す挙動をします。
691    pub fn render(&mut self) -> Result<()> {
692        // 描画するフレームバッファを取得
693        let current_surface_texture = self.surface.get_current_texture();
694
695        let output = if let wgpu::CurrentSurfaceTexture::Success(frame) = current_surface_texture {
696            frame
697        } else {
698            anyhow::bail!(
699                "`surface.get_current_texture` hasn't succeeded: {:?}.",
700                current_surface_texture
701            );
702        };
703        let view = output
704            .texture
705            .create_view(&wgpu::TextureViewDescriptor::default());
706
707        // アニメーション中はテキストブラシが更新位置を反映できるようにセクションを再キューする必要がある
708        // 補足: 呼び出し元(UI層)も各フレームで描画コマンドを再キューしているため、ここではアニメーション状態を返り値で通知するだけ
709
710        // GPUコマンドのエンコーダーの作成
711        let mut encoder = self
712            .device
713            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
714                label: Some("Render Encoder"),
715            });
716
717        // 描画パスの開始
718        {
719            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
720                label: Some("Render Pass"),
721                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
722                    view: &view,
723                    resolve_target: None,
724                    ops: wgpu::Operations {
725                        // 背景色をクリア
726                        load: wgpu::LoadOp::Clear(wgpu::Color {
727                            r: 1.0,
728                            g: 1.0,
729                            b: 1.0,
730                            a: 1.0,
731                        }),
732                        store: wgpu::StoreOp::Store,
733                    },
734                    depth_slice: None,
735                })],
736                depth_stencil_attachment: None,
737                occlusion_query_set: None,
738                timestamp_writes: None,
739                multiview_mask: None,
740            });
741
742            // 使用するシェーダー・設定をセット
743            render_pass.set_pipeline(&self.render_pipeline);
744            // 頂点バッファをセットして描画
745            if let Some(ref vertex_buffer) = self.vertex_buffer {
746                render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
747                render_pass.draw(0..self.num_vertices, 0..1);
748            }
749        }
750
751        // テキストをレンダリング
752        if let Some(tr) = &mut self.text_renderer {
753            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
754                label: Some("Text Render Pass"),
755                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
756                    view: &view,
757                    resolve_target: None,
758                    ops: wgpu::Operations {
759                        load: wgpu::LoadOp::Load,
760                        store: wgpu::StoreOp::Store,
761                    },
762                    depth_slice: None,
763                })],
764                depth_stencil_attachment: None,
765                occlusion_query_set: None,
766                timestamp_writes: None,
767                multiview_mask: None,
768            });
769            tr.draw(&mut rpass);
770        }
771
772        // コマンドをGPUに送信
773        self.queue.submit(std::iter::once(encoder.finish()));
774
775        // フレームを画面に表示
776        output.present();
777
778        Ok(())
779    }
780
781    fn update_vertices(
782        &mut self,
783        old_size: winit::dpi::PhysicalSize<u32>,
784        new_size: winit::dpi::PhysicalSize<u32>,
785    ) {
786        let old_w = old_size.width as f32;
787        let old_h = old_size.height as f32;
788        let new_w = new_size.width as f32;
789        let new_h = new_size.height as f32;
790
791        let mut new_vertices = self.vertices.clone();
792
793        for vertex in new_vertices.iter_mut() {
794            // old NDC -> logical
795            let logical_x = (vertex.position[0] + 1.0) / 2.0 * old_w;
796            let logical_y = -(vertex.position[1] - 1.0) / 2.0 * old_h;
797
798            // logical -> new NDC
799            vertex.position[0] = (logical_x / new_w) * 2.0 - 1.0;
800            vertex.position[1] = -((logical_y / new_h) * 2.0 - 1.0);
801        }
802        self.set_vertex_buffer(new_vertices);
803    }
804
805    fn set_vertex_buffer(&mut self, vertices: Vec<Vertex>) {
806        // 頂点バッファを登録
807        if !vertices.is_empty() {
808            self.vertex_buffer = Some(self.device.create_buffer_init(
809                &wgpu::util::BufferInitDescriptor {
810                    label: Some("Vertex Buffer"),
811                    contents: bytemuck::cast_slice(&vertices),
812                    usage: wgpu::BufferUsages::VERTEX,
813                },
814            ));
815            self.num_vertices = vertices.len() as u32;
816        }
817        self.vertices = vertices;
818    }
819
820    pub fn set_scale_factor(&mut self, scale_factor: f64) {
821        self.scale_factor = scale_factor;
822    }
823}
824
825fn select_wgpu_backends() -> wgpu::Backends {
826    if let Ok(value) = env::var("ORINIUM_WGPU_BACKEND") {
827        match value.to_lowercase().as_str() {
828            "gl" | "opengl" => return wgpu::Backends::GL,
829            "vulkan" | "vk" => return wgpu::Backends::VULKAN,
830            "metal" => return wgpu::Backends::METAL,
831            "dx12" | "d3d12" => return wgpu::Backends::DX12,
832            "primary" => return wgpu::Backends::PRIMARY,
833            _ => {}
834        }
835    }
836
837    let is_wsl = env::var_os("WSL_DISTRO_NAME").is_some() || env::var_os("WSL_INTEROP").is_some();
838    let is_wayland = env::var_os("WAYLAND_DISPLAY").is_some();
839
840    if is_wsl && is_wayland {
841        // WSLg + Wayland can be unstable with Vulkan; prefer GL by default.
842        return wgpu::Backends::GL;
843    }
844
845    wgpu::Backends::PRIMARY
846}