mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 20:31:27 +00:00
Add support for importing OpenGL textures into Slint
This is the initial API for Slint to borrow OpenGL textures from the application for rendering.
This commit is contained in:
parent
9d2cc6ee5e
commit
eadfec64a3
16 changed files with 879 additions and 1 deletions
470
examples/opengl_texture/main.rs
Normal file
470
examples/opengl_texture/main.rs
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
use glow::HasContext;
|
||||
|
||||
macro_rules! define_scoped_binding {
|
||||
(struct $binding_ty_name:ident => $obj_name:path, $param_name:path, $binding_fn:ident, $target_name:path) => {
|
||||
struct $binding_ty_name {
|
||||
saved_value: Option<$obj_name>,
|
||||
gl: Rc<glow::Context>,
|
||||
}
|
||||
|
||||
impl $binding_ty_name {
|
||||
unsafe fn new(gl: &Rc<glow::Context>, new_binding: Option<$obj_name>) -> Self {
|
||||
let saved_value =
|
||||
NonZeroU32::new(gl.get_parameter_i32($param_name) as u32).map($obj_name);
|
||||
|
||||
gl.$binding_fn($target_name, new_binding);
|
||||
Self { saved_value, gl: gl.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for $binding_ty_name {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.gl.$binding_fn($target_name, self.saved_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(struct $binding_ty_name:ident => $obj_name:path, $param_name:path, $binding_fn:ident) => {
|
||||
struct $binding_ty_name {
|
||||
saved_value: Option<$obj_name>,
|
||||
gl: Rc<glow::Context>,
|
||||
}
|
||||
|
||||
impl $binding_ty_name {
|
||||
unsafe fn new(gl: &Rc<glow::Context>, new_binding: Option<$obj_name>) -> Self {
|
||||
let saved_value =
|
||||
NonZeroU32::new(gl.get_parameter_i32($param_name) as u32).map($obj_name);
|
||||
|
||||
gl.$binding_fn(new_binding);
|
||||
Self { saved_value, gl: gl.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for $binding_ty_name {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.gl.$binding_fn(self.saved_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_scoped_binding!(struct ScopedTextureBinding => glow::NativeTexture, glow::TEXTURE_BINDING_2D, bind_texture, glow::TEXTURE_2D);
|
||||
define_scoped_binding!(struct ScopedFrameBufferBinding => glow::NativeFramebuffer, glow::DRAW_FRAMEBUFFER_BINDING, bind_framebuffer, glow::DRAW_FRAMEBUFFER);
|
||||
define_scoped_binding!(struct ScopedRenderBufferBinding => glow::NativeRenderbuffer, glow::RENDERBUFFER_BINDING, bind_renderbuffer, glow::RENDERBUFFER);
|
||||
define_scoped_binding!(struct ScopedVBOBinding => glow::NativeBuffer, glow::ARRAY_BUFFER_BINDING, bind_buffer, glow::ARRAY_BUFFER);
|
||||
define_scoped_binding!(struct ScopedVAOBinding => glow::NativeVertexArray, glow::VERTEX_ARRAY_BINDING, bind_vertex_array);
|
||||
|
||||
struct DemoTexture {
|
||||
texture: glow::Texture,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fbo: glow::Framebuffer,
|
||||
stencil_rbo: glow::Renderbuffer,
|
||||
gl: Rc<glow::Context>,
|
||||
}
|
||||
|
||||
impl DemoTexture {
|
||||
unsafe fn new(gl: &Rc<glow::Context>, width: u32, height: u32) -> Self {
|
||||
let fbo = gl.create_framebuffer().expect("Unable to create framebuffer");
|
||||
|
||||
let texture = gl.create_texture().expect("Unable to allocate texture");
|
||||
|
||||
let _saved_texture_binding = ScopedTextureBinding::new(gl, Some(texture));
|
||||
|
||||
let old_unpack_alignment = gl.get_parameter_i32(glow::UNPACK_ALIGNMENT);
|
||||
let old_unpack_row_length = gl.get_parameter_i32(glow::UNPACK_ROW_LENGTH);
|
||||
let old_unpack_skip_pixels = gl.get_parameter_i32(glow::UNPACK_SKIP_PIXELS);
|
||||
let old_unpack_skip_rows = gl.get_parameter_i32(glow::UNPACK_SKIP_ROWS);
|
||||
|
||||
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
|
||||
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
|
||||
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
|
||||
gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, width as i32);
|
||||
gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, 0);
|
||||
gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, 0);
|
||||
|
||||
gl.tex_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::RGBA as _,
|
||||
width as _,
|
||||
height as _,
|
||||
0,
|
||||
glow::RGBA as _,
|
||||
glow::UNSIGNED_BYTE as _,
|
||||
None,
|
||||
);
|
||||
|
||||
let _saved_fbo_binding = ScopedFrameBufferBinding::new(gl, Some(fbo));
|
||||
|
||||
let stencil_rbo = gl.create_renderbuffer().expect("Unable to create stencil buffer");
|
||||
|
||||
gl.framebuffer_texture_2d(
|
||||
glow::FRAMEBUFFER,
|
||||
glow::COLOR_ATTACHMENT0,
|
||||
glow::TEXTURE_2D,
|
||||
Some(texture),
|
||||
0,
|
||||
);
|
||||
|
||||
{
|
||||
let _saved_rbo_binding = ScopedRenderBufferBinding::new(gl, Some(stencil_rbo));
|
||||
gl.renderbuffer_storage(
|
||||
glow::RENDERBUFFER,
|
||||
glow::STENCIL_INDEX8,
|
||||
width as _,
|
||||
height as _,
|
||||
);
|
||||
}
|
||||
|
||||
gl.framebuffer_renderbuffer(
|
||||
glow::FRAMEBUFFER,
|
||||
glow::STENCIL_ATTACHMENT,
|
||||
glow::RENDERBUFFER,
|
||||
Some(stencil_rbo),
|
||||
);
|
||||
|
||||
debug_assert_eq!(
|
||||
gl.check_framebuffer_status(glow::FRAMEBUFFER),
|
||||
glow::FRAMEBUFFER_COMPLETE
|
||||
);
|
||||
|
||||
gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, old_unpack_alignment);
|
||||
gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, old_unpack_row_length);
|
||||
gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, old_unpack_skip_pixels);
|
||||
gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, old_unpack_skip_rows);
|
||||
|
||||
Self { texture, width, height, fbo, stencil_rbo, gl: gl.clone() }
|
||||
}
|
||||
|
||||
unsafe fn with_texture_as_active_fbo<R>(&self, callback: impl FnOnce() -> R) -> R {
|
||||
let _saved_fbo = ScopedFrameBufferBinding::new(&self.gl, Some(self.fbo));
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DemoTexture {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.gl.delete_framebuffer(self.fbo);
|
||||
self.gl.delete_renderbuffer(self.stencil_rbo);
|
||||
self.gl.delete_texture(self.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DemoTexture> for slint::BorrowedOpenGLTexture {
|
||||
fn from(this: &DemoTexture) -> Self {
|
||||
slint::BorrowedOpenGLTexture::new(this.texture.0, (this.width, this.height).into())
|
||||
}
|
||||
}
|
||||
|
||||
struct DemoRenderer {
|
||||
gl: Rc<glow::Context>,
|
||||
program: glow::Program,
|
||||
vbo: glow::Buffer,
|
||||
vao: glow::VertexArray,
|
||||
effect_time_location: glow::UniformLocation,
|
||||
selected_light_color_position: glow::UniformLocation,
|
||||
start_time: instant::Instant,
|
||||
displayed_texture: DemoTexture,
|
||||
next_texture: DemoTexture,
|
||||
}
|
||||
|
||||
impl DemoRenderer {
|
||||
fn new(gl: glow::Context) -> Self {
|
||||
let gl = Rc::new(gl);
|
||||
unsafe {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
let (vertex_shader_source, fragment_shader_source) = (
|
||||
r#"#version 100
|
||||
attribute vec2 position;
|
||||
varying vec2 frag_position;
|
||||
void main() {
|
||||
frag_position = position;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}"#,
|
||||
r#"#version 100
|
||||
precision mediump float;
|
||||
varying vec2 frag_position;
|
||||
uniform vec3 selected_light_color;
|
||||
uniform float iTime;
|
||||
|
||||
float sdRoundBox(vec3 p, vec3 b, float r)
|
||||
{
|
||||
vec3 q = abs(p) - b;
|
||||
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
|
||||
}
|
||||
|
||||
vec3 rotateY(vec3 r, float angle)
|
||||
{
|
||||
mat3 rotation_matrix = mat3(cos(angle), 0, sin(angle), 0, 1, 0, -sin(angle), 0, cos(angle));
|
||||
return rotation_matrix * r;
|
||||
}
|
||||
|
||||
vec3 rotateZ(vec3 r, float angle) {
|
||||
mat3 rotation_matrix = mat3(cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1);
|
||||
return rotation_matrix * r;
|
||||
}
|
||||
|
||||
// Distance from the scene
|
||||
float scene(vec3 r)
|
||||
{
|
||||
vec3 pos = rotateZ(rotateY(r + vec3(-1.0, -1.0, 4.0), iTime), iTime);
|
||||
vec3 cube = vec3(0.5, 0.5, 0.5);
|
||||
float edge = 0.1;
|
||||
return sdRoundBox(pos, cube, edge);
|
||||
}
|
||||
|
||||
// https://iquilezles.org/articles/normalsSDF
|
||||
vec3 normal( in vec3 pos )
|
||||
{
|
||||
vec2 e = vec2(1.0,-1.0)*0.5773;
|
||||
const float eps = 0.0005;
|
||||
return normalize( e.xyy*scene( pos + e.xyy*eps ) +
|
||||
e.yyx*scene( pos + e.yyx*eps ) +
|
||||
e.yxy*scene( pos + e.yxy*eps ) +
|
||||
e.xxx*scene( pos + e.xxx*eps ) );
|
||||
}
|
||||
|
||||
#define ITERATIONS 90
|
||||
#define EPS 0.0001
|
||||
|
||||
vec4 render(vec2 fragCoord, vec3 light_color)
|
||||
{
|
||||
vec4 color = vec4(0, 0, 0, 1);
|
||||
|
||||
vec3 camera = vec3(1.0, 2.0, 1.0);
|
||||
vec3 p = vec3(fragCoord.x, fragCoord.y + 1.0, -1.0);
|
||||
vec3 dir = normalize(p - camera);
|
||||
|
||||
for(int i=0; i < ITERATIONS; i++)
|
||||
{
|
||||
float dist = scene(p);
|
||||
if(dist < EPS) {
|
||||
break;
|
||||
}
|
||||
p = p + dir * dist;
|
||||
}
|
||||
|
||||
vec3 surf_normal = normal(p);
|
||||
|
||||
vec3 light_position = vec3(2.0, 4.0, -0.5);
|
||||
float light = 7.0 + 2.0 * dot(surf_normal, light_position);
|
||||
light /= 0.2 * pow(length(light_position - p), 3.5);
|
||||
|
||||
return vec4(light * light_color.x, light * light_color.y, light * light_color.z, 1.0) * 2.0;
|
||||
}
|
||||
|
||||
/*
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord)
|
||||
{
|
||||
vec2 r = fragCoord.xy / iResolution.xy;
|
||||
r.x *= (iResolution.x / iResolution.y);
|
||||
fragColor = render(r, vec3(0.2, 0.5, 0.9));
|
||||
}
|
||||
*/
|
||||
|
||||
void main() {
|
||||
vec2 r = vec2(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y);
|
||||
gl_FragColor = render(r, selected_light_color);
|
||||
}"#,
|
||||
);
|
||||
|
||||
let shader_sources = [
|
||||
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||
];
|
||||
|
||||
let mut shaders = Vec::with_capacity(shader_sources.len());
|
||||
|
||||
for (shader_type, shader_source) in shader_sources.iter() {
|
||||
let shader = gl.create_shader(*shader_type).expect("Cannot create shader");
|
||||
gl.shader_source(shader, shader_source);
|
||||
gl.compile_shader(shader);
|
||||
if !gl.get_shader_compile_status(shader) {
|
||||
panic!("{}", gl.get_shader_info_log(shader));
|
||||
}
|
||||
gl.attach_shader(program, shader);
|
||||
shaders.push(shader);
|
||||
}
|
||||
|
||||
gl.link_program(program);
|
||||
if !gl.get_program_link_status(program) {
|
||||
panic!("{}", gl.get_program_info_log(program));
|
||||
}
|
||||
|
||||
for shader in shaders {
|
||||
gl.detach_shader(program, shader);
|
||||
gl.delete_shader(shader);
|
||||
}
|
||||
|
||||
let effect_time_location = gl.get_uniform_location(program, "iTime").unwrap();
|
||||
let selected_light_color_position =
|
||||
gl.get_uniform_location(program, "selected_light_color").unwrap();
|
||||
let position_location = gl.get_attrib_location(program, "position").unwrap();
|
||||
|
||||
let vbo = gl.create_buffer().expect("Cannot create buffer");
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
|
||||
|
||||
let vertices = [-1.0f32, 1.0f32, -1.0f32, -1.0f32, 1.0f32, 1.0f32, 1.0f32, -1.0f32];
|
||||
|
||||
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, vertices.align_to().1, glow::STATIC_DRAW);
|
||||
|
||||
let vao = gl.create_vertex_array().expect("Cannot create vertex array");
|
||||
gl.bind_vertex_array(Some(vao));
|
||||
gl.enable_vertex_attrib_array(position_location);
|
||||
gl.vertex_attrib_pointer_f32(position_location, 2, glow::FLOAT, false, 8, 0);
|
||||
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||
gl.bind_vertex_array(None);
|
||||
|
||||
let displayed_texture = DemoTexture::new(&gl, 320, 200);
|
||||
let next_texture = DemoTexture::new(&gl, 320, 200);
|
||||
|
||||
Self {
|
||||
gl,
|
||||
program,
|
||||
effect_time_location,
|
||||
selected_light_color_position,
|
||||
vbo,
|
||||
vao,
|
||||
start_time: instant::Instant::now(),
|
||||
displayed_texture,
|
||||
next_texture,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DemoRenderer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.gl.delete_program(self.program);
|
||||
self.gl.delete_vertex_array(self.vao);
|
||||
self.gl.delete_buffer(self.vbo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DemoRenderer {
|
||||
fn render(
|
||||
&mut self,
|
||||
light_red: f32,
|
||||
light_green: f32,
|
||||
light_blue: f32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> slint::BorrowedOpenGLTexture {
|
||||
unsafe {
|
||||
let gl = &self.gl;
|
||||
|
||||
gl.use_program(Some(self.program));
|
||||
|
||||
let _saved_vbo = ScopedVBOBinding::new(gl, Some(self.vbo));
|
||||
let _saved_vao = ScopedVAOBinding::new(gl, Some(self.vao));
|
||||
|
||||
if self.next_texture.width != width || self.next_texture.height != height {
|
||||
let mut new_texture = DemoTexture::new(gl, width, height);
|
||||
std::mem::swap(&mut self.next_texture, &mut new_texture);
|
||||
}
|
||||
|
||||
self.next_texture.with_texture_as_active_fbo(|| {
|
||||
let mut saved_viewport: [i32; 4] = [0, 0, 0, 0];
|
||||
gl.get_parameter_i32_slice(glow::VIEWPORT, &mut saved_viewport);
|
||||
|
||||
gl.viewport(0, 0, self.next_texture.width as _, self.next_texture.height as _);
|
||||
let elapsed = self.start_time.elapsed().as_millis() as f32 / 500.;
|
||||
gl.uniform_1_f32(Some(&self.effect_time_location), elapsed);
|
||||
|
||||
gl.uniform_3_f32(
|
||||
Some(&self.selected_light_color_position),
|
||||
light_red,
|
||||
light_green,
|
||||
light_blue,
|
||||
);
|
||||
|
||||
gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
gl.viewport(
|
||||
saved_viewport[0],
|
||||
saved_viewport[1],
|
||||
saved_viewport[2],
|
||||
saved_viewport[3],
|
||||
);
|
||||
});
|
||||
|
||||
gl.use_program(None);
|
||||
}
|
||||
|
||||
let result_texture = (&self.next_texture).into();
|
||||
|
||||
std::mem::swap(&mut self.next_texture, &mut self.displayed_texture);
|
||||
|
||||
result_texture
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new().unwrap();
|
||||
|
||||
let mut underlay = None;
|
||||
|
||||
let app_weak = app.as_weak();
|
||||
|
||||
if let Err(error) = app.window().set_rendering_notifier(move |state, graphics_api| {
|
||||
// eprintln!("rendering state {:#?}", state);
|
||||
|
||||
match state {
|
||||
slint::RenderingState::RenderingSetup => {
|
||||
let context = match graphics_api {
|
||||
slint::GraphicsAPI::NativeOpenGL { get_proc_address } => unsafe {
|
||||
glow::Context::from_loader_function_cstr(|s| get_proc_address(s))
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
underlay = Some(DemoRenderer::new(context))
|
||||
}
|
||||
slint::RenderingState::BeforeRendering => {
|
||||
if let (Some(underlay), Some(app)) = (underlay.as_mut(), app_weak.upgrade()) {
|
||||
let texture = underlay.render(
|
||||
app.get_selected_red(),
|
||||
app.get_selected_green(),
|
||||
app.get_selected_blue(),
|
||||
app.get_requested_texture_width() as u32,
|
||||
app.get_requested_texture_height() as u32,
|
||||
);
|
||||
app.set_texture(slint::Image::from(texture));
|
||||
app.window().request_redraw();
|
||||
}
|
||||
}
|
||||
slint::RenderingState::AfterRendering => {}
|
||||
slint::RenderingState::RenderingTeardown => {
|
||||
drop(underlay.take());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}) {
|
||||
match error {
|
||||
slint::SetRenderingNotifierError::Unsupported => eprintln!("This example requires the use of the GL backend. Please run with the environment variable SLINT_BACKEND=GL set."),
|
||||
_ => unreachable!()
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
app.run().unwrap();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue