Skip to main content

orinium_browser/platform/renderer/
gpu.rs

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