1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Helper functions and useful types for interacting with WebRender

use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool};

use gleam::gl;
use glutin;
use webrender;
use webrender::api::*;

use window::Window;
use euclid::TypedPoint2D;
use resources;
use geometry::{Rect, Point, Size};

// Provides access to the WebRender context and API
pub(super) struct WebRenderContext {
    pub renderer: webrender::Renderer,
    pub render_api: RenderApi,
    pub epoch: Epoch,
    pub pipeline_id: PipelineId,
    pub document_id: DocumentId,
    pub device_pixel_ratio: f32,
    pub root_background_color: ColorF,
    // store frame ready event in case it is received after
    // update but before the event queue is waiting, otherwise
    // the event queue can go idle while there is a frame ready
    pub frame_ready: Arc<AtomicBool>,
}

// Context needed for widgets to draw or update resources in a particular frame
pub struct RenderBuilder {
    pub builder: DisplayListBuilder,
    pub resources: ResourceUpdates,
}

impl WebRenderContext {
    pub fn new(window: &mut Window, events_loop: &glutin::EventsLoop) -> Self {
        let gl = window.gl();
        println!("OpenGL version {}", gl.get_string(gl::VERSION));
        println!("HiDPI factor {}", window.hidpi_factor());

        let opts = webrender::RendererOptions {
            resource_override_path: None,
            debug: true,
            precache_shaders: false,
            device_pixel_ratio: window.hidpi_factor(),
            .. webrender::RendererOptions::default()
        };

        let frame_ready = Arc::new(AtomicBool::new(false));
        let notifier = Box::new(Notifier::new(events_loop.create_proxy(), Arc::clone(&frame_ready)));

        let (mut renderer, sender) = webrender::Renderer::new(gl, notifier, opts).unwrap();
        let api = sender.create_api();
        resources::init_resources(sender);
        let document_id = api.add_document(window.size_px(), 0);

        renderer.set_external_image_handler(Box::new(LimnExternalImageHandler));

        let epoch = Epoch(0);
        let root_background_color = ColorF::new(0.8, 0.8, 0.8, 1.0);

        let pipeline_id = PipelineId(0, 0);
        let mut txn = Transaction::new();
        txn.set_root_pipeline(pipeline_id);
        api.send_transaction(document_id, txn);

        WebRenderContext {
            renderer: renderer,
            render_api: api,
            epoch: epoch,
            pipeline_id: pipeline_id,
            document_id: document_id,
            device_pixel_ratio: window.hidpi_factor(),
            root_background_color: root_background_color,
            frame_ready: frame_ready,
        }
    }
    pub fn deinit(self) {
        self.renderer.deinit();
    }
    pub fn render_builder(&mut self, window_size: LayoutSize) -> RenderBuilder {
        let builder = DisplayListBuilder::new(self.pipeline_id, window_size);
        RenderBuilder {
            builder: builder,
            resources: ResourceUpdates::new(),
        }
    }
    pub fn set_display_list(&mut self, builder: DisplayListBuilder, resources: ResourceUpdates, window_size: LayoutSize) {
        let mut txn = Transaction::new();
        txn.set_display_list(
            self.epoch,
            Some(self.root_background_color),
            window_size,
            builder.finalize(),
            true,
        );
        txn.update_resources(resources);
        self.render_api.send_transaction(self.document_id, txn);
    }
    pub fn generate_frame(&mut self) {
        let mut txn = Transaction::new();
        txn.generate_frame();
        self.render_api.send_transaction(self.document_id, txn);
    }
    pub fn frame_ready(&mut self) -> bool {
        self.frame_ready.load(atomic::Ordering::Acquire)
    }
    // if there is a frame ready, update current frame and render it, otherwise, does nothing
    pub fn update(&mut self, window_size: DeviceUintSize) {
        self.frame_ready.store(false, atomic::Ordering::Release);
        self.renderer.update();
        self.renderer.render(window_size).unwrap();
    }
    pub fn toggle_flags(&mut self, toggle_flags: webrender::DebugFlags) {
        let mut flags = self.renderer.get_debug_flags();
        flags.toggle(toggle_flags);
        self.renderer.set_debug_flags(flags);
    }
    pub fn window_resized(&mut self, size: DeviceUintSize) {
        let window_rect = DeviceUintRect::new(TypedPoint2D::zero(), size);
        self.render_api.set_window_parameters(self.document_id, size, window_rect, self.device_pixel_ratio);
    }
}

struct Notifier {
    events_proxy: glutin::EventsLoopProxy,
    frame_ready: Arc<AtomicBool>,
}
impl Notifier {
    fn new(events_proxy: glutin::EventsLoopProxy, frame_ready: Arc<AtomicBool>) -> Self {
        Notifier {
            events_proxy: events_proxy,
            frame_ready: frame_ready,
        }
    }
}

impl RenderNotifier for Notifier {
    fn wake_up(&self) {
        debug!("wakeup renderer");
        #[cfg(not(target_os = "android"))]
        self.events_proxy.wakeup().ok();
        self.frame_ready.store(true, atomic::Ordering::Release);
    }

    fn new_document_ready(&self, _: DocumentId, _: bool, _: bool) {
        self.wake_up();
    }

    fn clone(&self) -> Box<RenderNotifier + 'static> {
        Box::new(Notifier::new(self.events_proxy.clone(), self.frame_ready.clone()))
    }
}

pub fn draw_rect_outline<C: Into<ColorF>>(rect: Rect, color: C, renderer: &mut RenderBuilder) {
    let widths = BorderWidths { left: 1.0, right: 1.0, top: 1.0, bottom: 1.0 };
    let side = BorderSide { color: color.into(), style: BorderStyle::Solid };
    let border = NormalBorder { left: side, right: side, top: side, bottom: side, radius: BorderRadius::zero() };
    let details = BorderDetails::Normal(border);
    let info = PrimitiveInfo::new(rect);
    renderer.builder.push_border(&info, widths, details);
}

pub fn draw_horizontal_line<C: Into<ColorF>>(baseline: f32, start: f32, end: f32, color: C, renderer: &mut RenderBuilder) {
    draw_rect_outline(Rect::new(Point::new(start, baseline), Size::new(end - start, 0.0)), color, renderer);
}

// This weird thing is required just to pass a texture's id to WebRender
struct LimnExternalImageHandler;

impl webrender::ExternalImageHandler for LimnExternalImageHandler {
    // Do not perform any actual locking since rendering happens on the main thread
    fn lock(&mut self, key: ExternalImageId, _channel_index: u8) -> webrender::ExternalImage {
        let descriptor = resources::resources().image_loader.texture_descriptors[&key.0];
        webrender::ExternalImage {
            uv: TexelRect {
                uv0: TypedPoint2D::zero(),
                uv1: TypedPoint2D::<f32, DevicePixel>::new(descriptor.width as f32, descriptor.height as f32),
            },
            source: webrender::ExternalImageSource::NativeTexture(key.0 as _),
        }
    }

    fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {
    }
}