Skip to main content

orinium_browser/platform/renderer/
gpu.rs

1//! wgpuを使用してGPUで描画するためのコンテキストと処理を提供するモジュール
2
3use crate::engine::renderer_model::DrawCommand;
4use anyhow::Result;
5use std::env;
6use std::sync::Arc;
7use wgpu::util::DeviceExt;
8use winit::window::Window;
9
10use super::image::ImageRenderer;
11use super::mesh::{self, MeshBuilder, Vertex};
12use super::text::global_font;
13use super::text::text_renderer::TextRenderer;
14
15/// GPU描画コンテキスト
16pub struct GpuRenderer {
17    /// GPUの描画対象
18    surface: wgpu::Surface<'static>,
19    /// GPUの論理デバイス
20    device: wgpu::Device,
21    /// コマンド送信用キュー
22    queue: wgpu::Queue,
23    /// サーフェス設定、解像度・フォーマットなどのフレームバッファ設定
24    config: wgpu::SurfaceConfiguration,
25    /// WindowSize
26    size: winit::dpi::PhysicalSize<u32>,
27    /// ディスプレイ倍率
28    scale_factor: f64,
29    /// RenderPipelin(頂点 to ピクセル)
30    render_pipeline: wgpu::RenderPipeline,
31    /// 頂点バッファ
32    vertex_buffer: Option<wgpu::Buffer>,
33    /// 描画命令から頂点・テキストセクションを生成するジオメトリ層
34    mesh_builder: MeshBuilder,
35    /// コマンド順を保持する描画項目
36    draw_items: Vec<mesh::DrawItem>,
37
38    /// テキスト描画用ラッパー
39    text_renderer: Option<TextRenderer>,
40
41    /// Decoded page image renderer.
42    image_renderer: ImageRenderer,
43
44    /// テキストカリングを有効にする
45    enable_text_culling: bool,
46}
47
48impl Vertex {
49    fn desc() -> wgpu::VertexBufferLayout<'static> {
50        wgpu::VertexBufferLayout {
51            array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
52            step_mode: wgpu::VertexStepMode::Vertex,
53            attributes: &[
54                wgpu::VertexAttribute {
55                    offset: 0,
56                    shader_location: 0,
57                    format: wgpu::VertexFormat::Float32x3,
58                },
59                wgpu::VertexAttribute {
60                    offset: size_of::<[f32; 3]>() as wgpu::BufferAddress,
61                    shader_location: 1,
62                    format: wgpu::VertexFormat::Float32x4,
63                },
64            ],
65        }
66    }
67}
68
69impl GpuRenderer {
70    /// 新しいGPUレンダラーを作成
71    pub async fn new(window: Arc<Window>, font_path: Option<&str>) -> Result<Self> {
72        let size = window.inner_size();
73        let scale_factor = window.scale_factor();
74
75        // GPUドライバとの通信インスタンス
76        // wgpuインスタンスの作成
77        //
78        // [`InstanceDescriptor::new_with_out_display_hundler`] の実装を参考に
79        // backends 選択は [`select_wgpu_backends`] を使った実装。
80        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
81            backends: select_wgpu_backends(),
82            flags: Default::default(),
83            memory_budget_thresholds: Default::default(),
84            backend_options: Default::default(),
85            display: None,
86        });
87
88        // OSウィンドウとGPUの描画対象(サーフェス)を関連付ける
89        // サーフェスの作成
90        let surface = instance.create_surface(Arc::clone(&window))?;
91
92        // 利用可能なGPU(物理デバイス)アダプターの取得
93        let adapter = instance
94            .request_adapter(&wgpu::RequestAdapterOptions {
95                power_preference: wgpu::PowerPreference::default(),
96                compatible_surface: Some(&surface),
97                force_fallback_adapter: false,
98                // This is currently only used for the browser's rendering backend, but we
99                // enable limit bucketing preemptively in case WebGPU is exposed to web content
100                // in the future.
101                apply_limit_buckets: true,
102            })
103            .await?;
104
105        // デバイスとキューの作成
106        let (device, queue) = adapter
107            .request_device(&wgpu::DeviceDescriptor {
108                label: None,
109                required_features: wgpu::Features::empty(),
110                required_limits: wgpu::Limits::default(),
111                experimental_features: Default::default(),
112                memory_hints: wgpu::MemoryHints::default(),
113                trace: Default::default(),
114            })
115            .await?;
116
117        // サーフェス設定
118        // フレームバッファ設定(解像度・フォーマットなど)
119        let surface_caps = surface.get_capabilities(&adapter);
120        let surface_format = surface_caps
121            .formats
122            .iter()
123            .copied()
124            .find(|f| f.is_srgb())
125            .unwrap_or(surface_caps.formats[0]);
126
127        let config = wgpu::SurfaceConfiguration {
128            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
129            format: surface_format,
130            // Use automatic color space selection until browser-level color
131            // management and CSS color spaces are implemented.
132            color_space: wgpu::SurfaceColorSpace::Auto,
133            width: size.width,
134            height: size.height,
135            present_mode: surface_caps.present_modes[0],
136            alpha_mode: surface_caps.alpha_modes[0],
137            view_formats: vec![],
138            desired_maximum_frame_latency: 2,
139        };
140        surface.configure(&device, &config);
141
142        // シェーダーの読み込み
143        // シェーダーモジュールの作成
144        // vertex/fragment for main pipeline
145        let main_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
146            label: Some("Main Shader"),
147            source: wgpu::ShaderSource::Wgsl(include_str!("shader/main.wgsl").into()),
148        });
149
150        // --- レンダーパイプライン(頂点→ピクセル変換のルール)の作成 ---
151        let render_pipeline_layout =
152            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
153                label: Some("Render Pipeline Layout"),
154                bind_group_layouts: &[],
155                immediate_size: 0,
156            });
157
158        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
159            label: Some("Render Pipeline"),
160            layout: Some(&render_pipeline_layout),
161            cache: None,
162            vertex: wgpu::VertexState {
163                module: &main_shader,
164                entry_point: Some("vs_main"),
165                buffers: &[Some(Vertex::desc())],
166                compilation_options: wgpu::PipelineCompilationOptions::default(),
167            },
168            fragment: Some(wgpu::FragmentState {
169                module: &main_shader,
170                entry_point: Some("fs_main"),
171                targets: &[Some(wgpu::ColorTargetState {
172                    format: config.format,
173                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
174                    write_mask: wgpu::ColorWrites::ALL,
175                })],
176                compilation_options: wgpu::PipelineCompilationOptions::default(),
177            }),
178            primitive: wgpu::PrimitiveState {
179                topology: wgpu::PrimitiveTopology::TriangleList,
180                strip_index_format: None,
181                front_face: wgpu::FrontFace::Ccw,
182                cull_mode: None, // 三角扇がカリングで消えちゃう...
183                polygon_mode: wgpu::PolygonMode::Fill,
184                unclipped_depth: false,
185                conservative: false,
186            },
187            depth_stencil: None,
188            multisample: wgpu::MultisampleState {
189                count: 1,
190                mask: !0,
191                alpha_to_coverage_enabled: false,
192            },
193            multiview_mask: None,
194        });
195        // --- レンダーパイプライン作成終了 ---
196
197        // テキスト描画用ラッパーの初期化。引数で渡されたフォントパスがあればグローバルフォントシステムに追加。
198        if let Some(p) = font_path {
199            global_font::load_global_font_path(p);
200        }
201        let text_renderer = match TextRenderer::new(&device, &queue, config.format) {
202            Ok(t) => Some(t),
203            Err(e) => {
204                log::warn!(target:"PRender::gpu::font" ,"no system font found for text renderer: {}", e);
205                None
206            }
207        };
208        let image_renderer = ImageRenderer::new(&device, config.format);
209
210        // Enable text culling by default, allow override by env var
211        let enable_text_culling = std::env::var("ORINIUM_TEXT_CULL").map_or(true, |v| v != "0");
212
213        Ok(Self {
214            surface,
215            device,
216            queue,
217            config,
218            size,
219            scale_factor,
220            render_pipeline,
221            vertex_buffer: None,
222            mesh_builder: MeshBuilder::new(
223                size.width as f32,
224                size.height as f32,
225                scale_factor as f32,
226            ),
227            draw_items: Vec::new(),
228            text_renderer,
229            image_renderer,
230            enable_text_culling,
231        })
232    }
233
234    /// ウィンドウサイズが変更された時の処理
235    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
236        if new_size.width > 0 && new_size.height > 0 {
237            log::info!(target:"PRender::gpu::resized", "Resized: {}x{}", new_size.width, new_size.height);
238
239            self.size = new_size;
240
241            self.config.width = new_size.width;
242            self.config.height = new_size.height;
243
244            self.surface.configure(&self.device, &self.config);
245
246            if let Some(tr) = &mut self.text_renderer {
247                tr.resize_view(
248                    self.config.width as f32,
249                    self.config.height as f32,
250                    &self.queue,
251                );
252            }
253        }
254    }
255
256    /// 描画命令を解析して頂点バッファやテキストキューに登録
257    pub fn parse_draw_commands(&mut self, commands: &[DrawCommand]) {
258        self.mesh_builder
259            .set_screen_size(self.size.width as f32, self.size.height as f32);
260        self.mesh_builder.set_scale_factor(self.scale_factor as f32);
261        self.mesh_builder.set_text_culling(self.enable_text_culling);
262
263        let mut text_source: Option<&mut dyn mesh::TextLayoutSource> = match &mut self.text_renderer
264        {
265            Some(tr) => Some(tr),
266            None => None,
267        };
268        let built = self.mesh_builder.build(commands, &mut text_source);
269        let vertices = built.vertices.clone();
270        let sections = built.sections.clone();
271        let images = built.images.clone();
272        self.draw_items.clone_from(&built.draw_items);
273
274        self.set_vertex_buffer(&vertices);
275        self.image_renderer.prepare(
276            &self.device,
277            &self.queue,
278            &images,
279            self.size.width as f32,
280            self.size.height as f32,
281        );
282
283        // テキストセクションをキューに追加
284        if let Some(tr) = &mut self.text_renderer {
285            tr.queue(&self.device, &self.queue, &sections).unwrap();
286        }
287    }
288
289    /// フレームを描画
290    ///
291    /// TODO:
292    /// [`wgpu::CurrentSurfaceTexture`] をよりよく処理する必要があります。
293    /// 現在は、 Success 時以外の結果を無視し、Errorにまとめて返す挙動をします。
294    pub fn render(&mut self) -> Result<()> {
295        // 描画するフレームバッファを取得
296        let current_surface_texture = self.surface.get_current_texture();
297
298        let output = if let wgpu::CurrentSurfaceTexture::Success(frame) = current_surface_texture {
299            frame
300        } else {
301            anyhow::bail!(
302                "`surface.get_current_texture` hasn't succeeded: {:?}.",
303                current_surface_texture
304            );
305        };
306        let view = output
307            .texture
308            .create_view(&wgpu::TextureViewDescriptor::default());
309
310        // アニメーション中はテキストブラシが更新位置を反映できるようにセクションを再キューする必要がある
311        // 補足: 呼び出し元(UI層)も各フレームで描画コマンドを再キューしているため、ここではアニメーション状態を返り値で通知するだけ
312
313        // GPUコマンドのエンコーダーの作成
314        let mut encoder = self
315            .device
316            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
317                label: Some("Render Encoder"),
318            });
319
320        // 描画パスの開始
321        {
322            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
323                label: Some("Render Pass"),
324                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
325                    view: &view,
326                    resolve_target: None,
327                    ops: wgpu::Operations {
328                        // 背景色をクリア
329                        load: wgpu::LoadOp::Clear(wgpu::Color {
330                            r: 1.0,
331                            g: 1.0,
332                            b: 1.0,
333                            a: 1.0,
334                        }),
335                        store: wgpu::StoreOp::Store,
336                    },
337                    depth_slice: None,
338                })],
339                depth_stencil_attachment: None,
340                occlusion_query_set: None,
341                timestamp_writes: None,
342                multiview_mask: None,
343            });
344
345            self.draw_in_order(&mut render_pass);
346        }
347
348        // コマンドをGPUに送信
349        self.queue.submit(std::iter::once(encoder.finish()));
350
351        // フレームを画面に表示
352        self.queue.present(output);
353
354        Ok(())
355    }
356
357    /// Draws every [`mesh::DrawItem`] in command order within a single render
358    /// pass, merging adjacent same-kind items to minimize pipeline switches.
359    fn draw_in_order<'a>(&mut self, render_pass: &mut wgpu::RenderPass<'a>) {
360        let mut i = 0;
361        while i < self.draw_items.len() {
362            match self.draw_items[i] {
363                mesh::DrawItem::Fill {
364                    vertex_start,
365                    vertex_count,
366                } => {
367                    let start = vertex_start;
368                    let mut count = vertex_count;
369                    i += 1;
370                    while i < self.draw_items.len() {
371                        if let mesh::DrawItem::Fill {
372                            vertex_start: s,
373                            vertex_count: c,
374                        } = self.draw_items[i]
375                        {
376                            if s == start + count {
377                                count += c;
378                                i += 1;
379                            } else {
380                                break;
381                            }
382                        } else {
383                            break;
384                        }
385                    }
386                    if let Some(ref vertex_buffer) = self.vertex_buffer {
387                        render_pass.set_pipeline(&self.render_pipeline);
388                        render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
389                        render_pass.draw(start..start + count, 0..1);
390                    }
391                }
392                mesh::DrawItem::Image(index) => {
393                    self.image_renderer.draw_at(render_pass, index);
394                    i += 1;
395                }
396                mesh::DrawItem::Text(_) => {
397                    let mut start = 0u32;
398                    let mut end = 0u32;
399                    let mut any = false;
400                    while i < self.draw_items.len() {
401                        if let mesh::DrawItem::Text(idx) = self.draw_items[i] {
402                            if let Some(tr) = &self.text_renderer
403                                && let Some((s, c)) = tr.section_range(idx)
404                            {
405                                if !any {
406                                    start = s;
407                                    any = true;
408                                }
409                                end = s + c;
410                            }
411                            i += 1;
412                        } else {
413                            break;
414                        }
415                    }
416                    if let Some(tr) = &mut self.text_renderer
417                        && any
418                        && end > start
419                    {
420                        tr.draw_range(render_pass, start, end - start);
421                    }
422                }
423            }
424        }
425    }
426
427    fn set_vertex_buffer(&mut self, vertices: &[Vertex]) {
428        // 頂点バッファを登録
429        if !vertices.is_empty() {
430            self.vertex_buffer = Some(self.device.create_buffer_init(
431                &wgpu::util::BufferInitDescriptor {
432                    label: Some("Vertex Buffer"),
433                    contents: bytemuck::cast_slice(vertices),
434                    usage: wgpu::BufferUsages::VERTEX,
435                },
436            ));
437        } else {
438            self.vertex_buffer = None;
439        }
440    }
441
442    pub fn set_scale_factor(&mut self, scale_factor: f64) {
443        self.scale_factor = scale_factor;
444    }
445}
446
447fn select_wgpu_backends() -> wgpu::Backends {
448    if let Ok(value) = env::var("ORINIUM_WGPU_BACKEND") {
449        match value.to_lowercase().as_str() {
450            "gl" | "opengl" => return wgpu::Backends::GL,
451            "vulkan" | "vk" => return wgpu::Backends::VULKAN,
452            "metal" => return wgpu::Backends::METAL,
453            "dx12" | "d3d12" => return wgpu::Backends::DX12,
454            "primary" => return wgpu::Backends::PRIMARY,
455            _ => {}
456        }
457    }
458
459    let is_wsl = env::var_os("WSL_DISTRO_NAME").is_some() || env::var_os("WSL_INTEROP").is_some();
460    let is_wayland = env::var_os("WAYLAND_DISPLAY").is_some();
461
462    if is_wsl && is_wayland {
463        // WSLg + Wayland can be unstable with Vulkan; prefer GL by default.
464        return wgpu::Backends::GL;
465    }
466
467    wgpu::Backends::PRIMARY
468}