use std::ffi::{CString};
use std::num::NonZeroU32;
use std::sync::{mpsc, Arc, Mutex};
use std::sync::mpsc::channel;
use std::{thread};
use ::gl::GetIntegerv;
use gl::types::{GLint};
use glutin::config::{Config};
use glutin::context::PossiblyCurrentContext;
use glutin::display::{Display};
use glutin::prelude::*;
use glutin::surface::{WindowSurface};
use log::info;
use measure_time::print_time;
use skia_safe::{ColorType, gpu, Surface};
use skia_safe::gpu::{backend_render_targets, SurfaceOrigin};
use skia_safe::gpu::gl::FramebufferInfo;
#[cfg(glx_backend)]
use winit::platform::x11;
use winit::window::Window;
use crate::context::{RenderContext, UserContext};
use crate::gl::context::GlRenderContext;
use crate::renderer::Renderer;
enum RenderMsg {
Updated,
Resize(u32, u32),
}
pub struct GlRenderer {
sender: mpsc::Sender<RenderMsg>,
render_context_wrapper: RenderContextWrapper,
}
#[derive(Copy, Clone)]
struct SurfaceParams {
num_samples: usize,
stencil_size: usize,
frame_buffer_info: FramebufferInfo,
}
struct GlContext {
user_context: Option<UserContext>,
surface: Surface,
render_context: GlRenderContext,
gl_surface: glutin::surface::Surface<WindowSurface>,
context: PossiblyCurrentContext,
}
unsafe impl Send for GlContext {}
impl GlRenderer {
pub fn new(
gl_display: &Display,
window: &Window,
gl_surface: glutin::surface::Surface<WindowSurface>,
context: PossiblyCurrentContext,
gl_config: &Config,
) -> Option<Self> {
unsafe {
gl::load_with(|s| {
gl_display
.get_proc_address(CString::new(s).unwrap().as_c_str())
});
let interface = gpu::gl::Interface::new_load_with(|name| {
if name == "eglGetCurrentDisplay" {
return std::ptr::null();
}
gl_display
.get_proc_address(CString::new(name).unwrap().as_c_str())
})?;
let mut gr_context = gpu::direct_contexts::make_gl(interface, None)?;
let fb_info = {
let mut fboid: GLint = 0;
GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid);
FramebufferInfo {
fboid: fboid.try_into().unwrap(),
format: gpu::gl::Format::RGBA8.into(),
..Default::default()
}
};
let num_samples = gl_config.num_samples() as usize;
let stencil_size = gl_config.stencil_size() as usize;
let surface_params = SurfaceParams {
num_samples,
stencil_size,
frame_buffer_info: fb_info,
};
let size = window.inner_size();
let size = (
size.width.try_into().expect("Could not convert width"),
size.height.try_into().expect("Could not convert height"),
);
let surface = {
Self::create_surface(size.0, size.1, &mut gr_context, &surface_params)
};
let context = context.make_not_current().ok()?.treat_as_possibly_current();
let render_context = GlRenderContext { gr_context };
let context = GlContext {
surface,
gl_surface,
render_context,
context,
user_context: Some(UserContext::new()),
};
let context = Arc::new(Mutex::new(context));
let drawer = Arc::new(Mutex::new(None));
let render_context_wrapper = RenderContextWrapper {
context,
surface_params,
drawer,
};
let (sender, receiver) = channel();
{
let render_context_wrapper = render_context_wrapper.clone();
thread::spawn(move || {
render_context_wrapper.make_current();
loop {
let msg = match receiver.recv() {
Ok(msg) => msg,
Err(_) => break,
};
match msg {
RenderMsg::Updated => {
render_context_wrapper.update();
}
RenderMsg::Resize(width, height) => {
render_context_wrapper.resize(width, height);
}
}
}
info!("GlRenderer thread stopped");
});
}
Some(Self { sender, render_context_wrapper })
}
}
pub fn draw(&mut self, drawer: Renderer, callback: Box<dyn FnOnce(bool) + Send + 'static>) {
self.render_context_wrapper.render(drawer, Box::new(callback));
self.sender.send(RenderMsg::Updated).unwrap();
}
pub fn resize(&self, _window: &Window, width: u32, height: u32) {
self.sender.send(RenderMsg::Resize(width, height)).unwrap();
}
fn create_surface(
width: i32,
height: i32,
gr_context: &mut gpu::DirectContext,
surface_params: &SurfaceParams,
) -> Surface {
let num_samples = surface_params.num_samples;
let stencil_size = surface_params.stencil_size;
let fb_info = surface_params.frame_buffer_info;
let size = (width, height);
let backend_render_target =
backend_render_targets::make_gl(size, num_samples, stencil_size, fb_info);
gpu::surfaces::wrap_backend_render_target(
gr_context,
&backend_render_target,
SurfaceOrigin::BottomLeft,
ColorType::RGBA8888,
None,
None,
)
.expect("Could not create skia surface")
}
}
struct RenderTask {
pub task: Renderer,
pub callback: Box<dyn FnOnce(bool) + Send + 'static>,
}
#[derive(Clone)]
pub struct RenderContextWrapper {
context: Arc<Mutex<GlContext>>,
drawer: Arc<Mutex<Option<RenderTask>>>,
surface_params: SurfaceParams,
}
impl RenderContextWrapper {
pub fn make_current(&self) {
let context = self.context.lock().unwrap();
context.context.make_current(&context.gl_surface).unwrap();
}
pub fn resize(&self, width: u32, height: u32, ) {
print_time!("resize time");
let mut context = self.context.lock().unwrap();
let sf_params = self.surface_params.clone();
let sf = GlRenderer::create_surface(
width as i32,
height as i32,
&mut context.render_context.gr_context,
&sf_params,
);
context.surface = sf;
context.gl_surface.resize(
&context.context,
NonZeroU32::new(width.max(1)).unwrap(),
NonZeroU32::new(height.max(1)).unwrap(),
);
}
pub fn render(&self, drawer: Renderer, callback: Box<dyn FnOnce(bool) + Send + 'static>) {
let mut drawer_mg = self.drawer.lock().unwrap();
if let Some(task) = drawer_mg.take() {
(task.callback)(false);
}
drawer_mg.replace(RenderTask { task: drawer, callback });
}
fn update(&self) {
let mut context = {
self.context.lock().unwrap()
};
let mut rc = context.render_context.clone();
let callback = {
let drawer = {
let mut drawer_arc = self.drawer.lock().unwrap();
drawer_arc.take()
};
if let Some(drawer) = drawer {
let mut user_context = context.user_context.take().unwrap();
let canvas = context.surface.canvas();
let mut rc = RenderContext::new(&mut rc, &mut user_context);
(drawer.task).render(canvas, &mut rc);
context.user_context = Some(user_context);
drawer.callback
} else {
return;
}
};
{
context.render_context.gr_context.flush_and_submit();
}
{
if let Err(err) = context.gl_surface.swap_buffers(&context.context) {
log::error!("Failed to swap buffers after render: {}", err);
}
}
callback(true);
}
}