// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
// This file is a part of `piet-hardware`.
//
// `piet-hardware` is free software: you can redistribute it and/or modify it under the
// terms of either:
//
// * GNU Lesser General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
// * Mozilla Public License as published by the Mozilla Foundation, version 2.
//
// `piet-hardware` is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License and the Mozilla
// Public License along with `piet-hardware`. If not, see .
//! An example that uses the `gl` crate to render to a `winit` window.
//!
//! This uses `glutin` crate to set up a GL context, `winit` to create a window, and the `gl`
//! crate to make GL calls.
//!
//! This example exists mostly to give an example of how a `GpuContext` can be implemented.
//! If you actually want to use `piet` with OpenGL, consider the `piet-glow` crate.
use glutin::config::ConfigTemplateBuilder;
use glutin::context::{ContextApi, ContextAttributesBuilder, Version};
use glutin::display::GetGlDisplay;
use glutin::prelude::*;
use glutin_winit::{DisplayBuilder, GlWindow};
use piet::kurbo::{Affine, BezPath, Point, Rect};
use piet::RenderContext as _;
use piet_hardware::gpu_types::{AreaCapture, BufferPush, SubtextureWrite, TextureWrite};
use raw_window_handle::HasRawWindowHandle;
use winit::dpi::PhysicalSize;
use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
use std::cell::Cell;
use std::ffi::CString;
use std::fmt;
use std::mem;
use std::num::NonZeroU32;
use web_time::{Duration, Instant};
const TEST_IMAGE: &[u8] = include_bytes!("test-image.png");
fn main() -> Result<(), Box> {
env_logger::init();
// Create the winit event loop.
let event_loop = EventLoop::new();
let mut size = PhysicalSize::new(800, 600);
let make_window_builder = move || {
WindowBuilder::new()
.with_title("piet-hardware example")
.with_transparent(true)
.with_inner_size(size)
};
// If we're on Windows, start with the window.
let window = if cfg!(windows) {
Some(make_window_builder())
} else {
None
};
// Start building an OpenGL display.
let display = DisplayBuilder::new().with_window_builder(window);
// Look for a config that supports transparency and has a good sample count.
let (mut window, gl_config) = display.build(
&event_loop,
ConfigTemplateBuilder::new().with_alpha_size(8),
|configs| {
configs
.reduce(|accum, config| {
let transparency_check = config.supports_transparency().unwrap_or(false)
& !accum.supports_transparency().unwrap_or(false);
if transparency_check || config.num_samples() > accum.num_samples() {
config
} else {
accum
}
})
.unwrap()
},
)?;
// Try to build a several different contexts.
let window_handle = window.as_ref().map(|w| w.raw_window_handle());
let contexts = [
ContextAttributesBuilder::new().build(window_handle),
ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(None))
.build(window_handle),
ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(Some(Version::new(2, 0))))
.build(window_handle),
];
let display = gl_config.display();
let gl_handler = (|| {
// Try to build a context for each config.
for context in &contexts {
if let Ok(gl_context) = unsafe { display.create_context(&gl_config, context) } {
return Ok(gl_context);
}
}
// If we couldn't build a context, return an error.
Err(Box::::from(
"Could not create a context",
))
})()?;
// Set up data for the window.
let framerate = Duration::from_millis({
let framerate = 1.0 / 60.0;
(framerate * 1000.0) as u64
});
let mut next_frame = Instant::now() + framerate;
let mut state = None;
let mut renderer = None;
let mut not_current_gl_context = Some(gl_handler);
// Load the image.
let (image_width, image_height, image_data) = {
let image = image::load_from_memory(TEST_IMAGE).unwrap();
let image = image.to_rgba8();
let (width, height) = image.dimensions();
let data = image.into_raw();
(width, height, data)
};
// Drawing data.
let star = generate_five_pointed_star((0.0, 0.0).into(), 75.0, 150.0);
let mut solid_red = None;
let mut outline = None;
let mut image = None;
let mut tick = 0;
let mut num_frames = 0;
let mut last_second = Instant::now();
// Draw the window.
let mut draw =
move |ctx: &mut piet_hardware::RenderContext<'_, 'static, 'static, GlContext>| {
ctx.clear(None, piet::Color::AQUA);
let outline = outline.get_or_insert_with(|| ctx.solid_brush(piet::Color::BLACK));
// Draw a rotating star.
ctx.with_save(|ctx| {
ctx.transform({
let rotation = Affine::rotate((tick as f64) * 0.02);
let translation = Affine::translate((200.0, 200.0));
translation * rotation
});
let solid_red = solid_red
.get_or_insert_with(|| ctx.solid_brush(piet::Color::rgb8(0x39, 0xe5, 0x8a)));
ctx.fill(&star, solid_red);
ctx.stroke(&star, outline, 5.0);
Ok(())
})
.unwrap();
// Draw a moving image.
{
let cos_curve = |x: f64, amp: f64, freq: f64| {
let x = x * std::f64::consts::PI * freq;
x.cos() * amp
};
let sin_curve = |x: f64, amp: f64, freq: f64| {
let x = x * std::f64::consts::PI * freq;
x.sin() * amp
};
let posn_shift_x = cos_curve(tick as f64, 50.0, 0.01);
let posn_shift_y = sin_curve(tick as f64, 50.0, 0.01);
let posn_x = 450.0 + posn_shift_x;
let posn_y = 150.0 + posn_shift_y;
let size_shift_x = cos_curve(tick as f64, 25.0, 0.02);
let size_shift_y = sin_curve(tick as f64, 25.0, 0.02);
let size_x = 100.0 + size_shift_x;
let size_y = 100.0 + size_shift_y;
let target_rect = Rect::new(posn_x, posn_y, posn_x + size_x, posn_y + size_y);
let image_handle = image.get_or_insert_with(|| {
ctx.make_image(
image_width as usize,
image_height as usize,
&image_data,
piet::ImageFormat::RgbaSeparate,
)
.unwrap()
});
ctx.draw_image(image_handle, target_rect, piet::InterpolationMode::Bilinear);
// Also draw a subset of the image.
let source_rect = Rect::new(
25.0 + posn_shift_x,
25.0 + posn_shift_y,
100.0 + posn_shift_x,
100.0 + posn_shift_y,
);
let target_rect = Rect::from_origin_size((625.0, 50.0), (100.0, 100.0));
ctx.draw_image_area(
image_handle,
source_rect,
target_rect,
piet::InterpolationMode::Bilinear,
);
ctx.stroke(target_rect, outline, 5.0);
}
tick += 1;
num_frames += 1;
if Instant::now().duration_since(last_second) >= Duration::from_secs(1) {
last_second = Instant::now();
println!("fps: {num_frames}");
num_frames = 0;
}
ctx.finish().unwrap();
ctx.status()
};
event_loop.run(move |event, target, control_flow| {
control_flow.set_wait_until(next_frame);
match event {
Event::Resumed => {
// We can now create windows.
let window = window.take().unwrap_or_else(|| {
let window_builder = make_window_builder();
glutin_winit::finalize_window(target, window_builder, &gl_config).unwrap()
});
let attrs = window.build_surface_attributes(Default::default());
let surface = unsafe {
gl_config
.display()
.create_window_surface(&gl_config, &attrs)
.unwrap()
};
// Make the context current.
let gl_context = not_current_gl_context
.take()
.unwrap()
.make_current(&surface)
.unwrap();
unsafe {
renderer
.get_or_insert_with(|| {
// Register the GL pointers if we can.
{
gl::load_with(|symbol| {
let symbol_cstr = CString::new(symbol).unwrap();
gl_config.display().get_proc_address(symbol_cstr.as_c_str())
});
piet_hardware::Source::new(GlContext::new(), &(), &()).unwrap()
}
})
.context()
.set_context();
}
state = Some((surface, window, gl_context));
}
Event::Suspended => {
// Destroy the window.
if let Some((.., context)) = state.take() {
not_current_gl_context = Some(context.make_not_current().unwrap());
}
if let Some(renderer) = &renderer {
renderer.context().unset_context();
}
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::Resized(new_size) => {
size = new_size;
if let Some((surface, _, context)) = &state {
surface.resize(
context,
NonZeroU32::new(size.width).unwrap(),
NonZeroU32::new(size.height).unwrap(),
);
}
}
_ => {}
},
Event::RedrawEventsCleared => {
if let (Some((surface, _, context)), Some(renderer)) = (&state, &mut renderer) {
// Create the render context.
let mut render_context =
renderer.render_context(&(), &(), size.width, size.height);
// Perform drawing.
draw(&mut render_context).unwrap();
// Swap buffers.
surface.swap_buffers(context).unwrap();
}
// Schedule the next frame.
next_frame += framerate;
}
_ => {}
}
})
}
fn generate_five_pointed_star(center: Point, inner_radius: f64, outer_radius: f64) -> BezPath {
let point_from_polar = |radius: f64, angle: f64| {
let x = center.x + radius * angle.cos();
let y = center.y + radius * angle.sin();
Point::new(x, y)
};
let one_fifth_circle = std::f64::consts::PI * 2.0 / 5.0;
let outer_points = (0..5).map(|i| point_from_polar(outer_radius, one_fifth_circle * i as f64));
let inner_points = (0..5).map(|i| {
point_from_polar(
inner_radius,
one_fifth_circle * i as f64 + one_fifth_circle / 2.0,
)
});
let mut points = outer_points.zip(inner_points).flat_map(|(a, b)| [a, b]);
// Set up the path.
let mut path = BezPath::new();
path.move_to(points.next().unwrap());
// Add the points to the path.
for point in points {
path.line_to(point);
}
// Close the path.
path.close_path();
path
}
/// The global OpenGL context.
struct GlContext {
/// Whether we have a context installed.
has_context: Cell,
/// A program for rendering.
render_program: gl::types::GLuint,
// Uniform locations.
u_transform: gl::types::GLint,
viewport_size: gl::types::GLint,
tex: gl::types::GLint,
mask: gl::types::GLint,
}
#[derive(Clone)]
struct GlVertexBuffer {
vbo: gl::types::GLuint,
ebo: gl::types::GLuint,
vao: gl::types::GLuint,
num_indices: Cell,
}
impl GlContext {
fn assert_context(&self) {
if !self.has_context.get() {
panic!("No GL context installed");
}
}
// SAFETY: Context must be current.
unsafe fn new() -> Self {
// Create the program.
let program = unsafe {
let vertex_shader = Self::compile_shader(gl::VERTEX_SHADER, VERTEX_SHADER).unwrap();
let fragment_shader =
Self::compile_shader(gl::FRAGMENT_SHADER, FRAGMENT_SHADER).unwrap();
let program = gl::CreateProgram();
gl::AttachShader(program, vertex_shader);
gl::AttachShader(program, fragment_shader);
gl::LinkProgram(program);
let mut success = gl::FALSE as gl::types::GLint;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut success);
if success == gl::FALSE as gl::types::GLint {
let mut len = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
gl::GetProgramInfoLog(program, len, std::ptr::null_mut(), buf.as_mut_ptr() as _);
buf.set_len((len as usize) - 1);
panic!(
"Could not link program: {}",
std::str::from_utf8(&buf).unwrap()
);
}
gl::DetachShader(program, vertex_shader);
gl::DetachShader(program, fragment_shader);
gl::DeleteShader(vertex_shader);
gl::DeleteShader(fragment_shader);
program
};
// Enable wireframe mode.
//unsafe {
// gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
//}
unsafe {
extern "system" fn debug_callback(
source: u32,
ty: u32,
id: u32,
severity: u32,
msg_len: i32,
msg: *const i8,
_user_param: *mut std::ffi::c_void,
) {
let source = match source {
gl::DEBUG_SOURCE_API => "API",
gl::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System",
gl::DEBUG_SOURCE_SHADER_COMPILER => "Shader Compiler",
gl::DEBUG_SOURCE_THIRD_PARTY => "Third Party",
gl::DEBUG_SOURCE_APPLICATION => "Application",
gl::DEBUG_SOURCE_OTHER => "Other",
_ => "Unknown",
};
let ty = match ty {
gl::DEBUG_TYPE_ERROR => "Error",
gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior",
gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior",
gl::DEBUG_TYPE_PORTABILITY => "Portability",
gl::DEBUG_TYPE_PERFORMANCE => "Performance",
gl::DEBUG_TYPE_MARKER => "Marker",
gl::DEBUG_TYPE_OTHER => "Other",
_ => "Unknown",
};
let message = {
let slice =
unsafe { std::slice::from_raw_parts(msg as *const u8, msg_len as usize) };
std::str::from_utf8(slice).unwrap()
};
match severity {
gl::DEBUG_SEVERITY_HIGH => {
log::error!("{ty}-{id} ({source}): {message}");
}
gl::DEBUG_SEVERITY_MEDIUM => {
log::warn!("{ty}-{id} ({source}): {message}");
}
gl::DEBUG_SEVERITY_LOW => {
log::info!("{ty}-{id} ({source}): {message}");
}
gl::DEBUG_SEVERITY_NOTIFICATION => {
log::debug!("{ty}-{id} ({source}): {message}");
}
_ => (),
};
}
// Set up a debug callback.
gl::Enable(gl::DEBUG_OUTPUT);
gl::DebugMessageCallback(Some(debug_callback), std::ptr::null());
}
// Get the uniform locations.
let u_transform = unsafe {
let name = CString::new("transform").unwrap();
gl::GetUniformLocation(program, name.as_ptr())
};
let viewport_size = unsafe {
let name = CString::new("viewportSize").unwrap();
gl::GetUniformLocation(program, name.as_ptr())
};
let tex = unsafe {
let name = CString::new("tex").unwrap();
gl::GetUniformLocation(program, name.as_ptr())
};
let mask = unsafe {
let name = CString::new("mask").unwrap();
gl::GetUniformLocation(program, name.as_ptr())
};
gl_error();
Self {
has_context: Cell::new(true),
render_program: program,
u_transform,
viewport_size,
tex,
mask,
}
}
fn unset_context(&self) {
self.has_context.set(false);
}
unsafe fn set_context(&self) {
self.has_context.set(true);
}
unsafe fn compile_shader(
shader_type: gl::types::GLenum,
source: &str,
) -> Result {
let shader = gl::CreateShader(shader_type);
let source = CString::new(source).unwrap();
gl::ShaderSource(shader, 1, &source.as_ptr(), std::ptr::null());
gl::CompileShader(shader);
let mut success = gl::FALSE as gl::types::GLint;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
if success == gl::FALSE as gl::types::GLint {
let mut len = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
gl::GetShaderInfoLog(
shader,
len,
std::ptr::null_mut(),
buf.as_mut_ptr() as *mut gl::types::GLchar,
);
buf.set_len((len as usize) - 1);
return Err(GlError(format!(
"Shader compilation failed: {}",
std::str::from_utf8(&buf).unwrap()
)));
}
Ok(shader)
}
}
#[derive(Debug)]
struct GlError(String);
impl fmt::Display for GlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GL error: {}", self.0)
}
}
impl std::error::Error for GlError {}
impl piet_hardware::GpuContext for GlContext {
type Device = ();
type Queue = ();
type Error = GlError;
type Texture = gl::types::GLuint;
type VertexBuffer = GlVertexBuffer;
fn clear(&mut self, _device: &(), _queue: &(), color: piet::Color) {
self.assert_context();
let (r, g, b, a) = color.as_rgba();
unsafe {
gl::Disable(gl::SCISSOR_TEST);
gl::ClearColor(r as f32, g as f32, b as f32, a as f32);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl_error();
}
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.assert_context();
unsafe {
gl::Flush();
gl_error();
Ok(())
}
}
fn create_texture(
&mut self,
_device: &(),
interpolation: piet::InterpolationMode,
repeat: piet_hardware::RepeatStrategy,
) -> Result {
self.assert_context();
unsafe {
let mut texture = 0;
gl::GenTextures(1, &mut texture);
gl::BindTexture(gl::TEXTURE_2D, texture);
let (min_filter, mag_filter) = match interpolation {
piet::InterpolationMode::NearestNeighbor => (gl::NEAREST, gl::NEAREST),
piet::InterpolationMode::Bilinear => (gl::LINEAR, gl::LINEAR),
};
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min_filter as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag_filter as _);
let (wrap_s, wrap_t) = match repeat {
piet_hardware::RepeatStrategy::Color(clr) => {
let (r, g, b, a) = clr.as_rgba();
gl::TexParameterfv(
gl::TEXTURE_2D,
gl::TEXTURE_BORDER_COLOR,
[r as f32, g as f32, b as f32, a as f32].as_ptr(),
);
(gl::CLAMP_TO_EDGE, gl::CLAMP_TO_EDGE)
}
piet_hardware::RepeatStrategy::Repeat => (gl::REPEAT, gl::REPEAT),
_ => panic!("unsupported repeat strategy"),
};
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, wrap_s as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, wrap_t as _);
Ok(texture as _)
}
}
fn write_texture(
&mut self,
TextureWrite {
device: (),
queue: (),
texture,
size,
format,
data,
}: TextureWrite<'_, Self>,
) {
self.assert_context();
unsafe {
gl::BindTexture(gl::TEXTURE_2D, *texture);
let (internal_format, format, ty) = match format {
piet::ImageFormat::RgbaSeparate => (gl::RGBA8, gl::RGBA, gl::UNSIGNED_BYTE),
piet::ImageFormat::RgbaPremul => (gl::RGBA8, gl::RGBA, gl::UNSIGNED_BYTE),
_ => panic!("unsupported image format"),
};
let (width, height) = size;
let data_ptr = data
.map(|data| data.as_ptr() as *const _)
.unwrap_or(std::ptr::null());
gl::TexImage2D(
gl::TEXTURE_2D,
0,
internal_format as _,
width as _,
height as _,
0,
format,
ty,
data_ptr,
);
}
}
fn write_subtexture(
&mut self,
SubtextureWrite {
device: (),
queue: (),
texture,
offset,
size,
format,
data,
}: SubtextureWrite<'_, Self>,
) {
self.assert_context();
unsafe {
gl::BindTexture(gl::TEXTURE_2D, *texture);
let (format, ty) = match format {
piet::ImageFormat::RgbaSeparate => (gl::RGBA, gl::UNSIGNED_BYTE),
_ => panic!("unsupported image format"),
};
let (width, height) = size;
let (x, y) = offset;
gl::TexSubImage2D(
gl::TEXTURE_2D,
0,
x as _,
y as _,
width as _,
height as _,
format,
ty,
data.as_ptr() as *const _,
);
}
}
fn set_texture_interpolation(
&mut self,
_device: &(),
texture: &Self::Texture,
interpolation: piet::InterpolationMode,
) {
self.assert_context();
let mode = match interpolation {
piet::InterpolationMode::Bilinear => gl::LINEAR,
piet::InterpolationMode::NearestNeighbor => gl::NEAREST,
};
unsafe {
gl::BindTexture(gl::TEXTURE_2D, *texture);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
mode as gl::types::GLint,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
mode as gl::types::GLint,
);
//gl::BindTexture(gl::TEXTURE_2D, 0);
}
}
fn capture_area(
&mut self,
AreaCapture {
device: (),
queue: (),
texture,
offset,
size,
bitmap_scale: scale,
}: AreaCapture<'_, Self>,
) -> Result<(), Self::Error> {
// Use glReadPixels to read into the texture.
self.assert_context();
unsafe {
let (x, y) = offset;
let (width, height) = size;
let (x, y, width, height) = (
(x as f64 * scale) as i32,
(y as f64 * scale) as i32,
(width as f64 * scale) as i32,
(height as f64 * scale) as i32,
);
let mut buffer = vec![0u8; (width * height * 4) as usize];
gl::ReadPixels(
x as _,
y as _,
width as _,
height as _,
gl::RGBA,
gl::UNSIGNED_BYTE,
buffer.as_mut_ptr() as *mut _,
);
gl_error();
// Flip the image.
let stride = width as usize * 4;
let mut row = vec![0u8; stride];
for i in 0..(height / 2) {
let top = i as usize;
let bottom = (height - i - 1) as usize;
let top_start = top * stride;
let bottom_start = bottom * stride;
row.copy_from_slice(&buffer[top_start..(top_start + stride)]);
buffer.copy_within(bottom_start..(bottom_start + stride), top_start);
buffer[bottom_start..(bottom_start + stride)].copy_from_slice(&row);
}
// Write the image to the texture.
self.write_subtexture(SubtextureWrite {
device: &(),
queue: &(),
texture,
offset,
size,
format: piet::ImageFormat::RgbaSeparate,
data: &buffer,
});
}
Ok(())
}
fn max_texture_size(&mut self, _device: &()) -> (u32, u32) {
self.assert_context();
unsafe {
let mut side = 0;
gl::GetIntegerv(gl::MAX_TEXTURE_SIZE, &mut side);
(side as u32, side as u32)
}
}
fn create_vertex_buffer(&mut self, _device: &()) -> Result {
self.assert_context();
unsafe {
let mut buffers = [0; 2];
gl::GenBuffers(2, buffers.as_mut_ptr());
let [vbo, ebo] = buffers;
// Set up the vertex array object.
let mut vao = 0;
gl::GenVertexArrays(1, &mut vao);
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
gl::UseProgram(self.render_program);
let stride = std::mem::size_of::() as _;
// Set up the layout:
// - vec2 of floats for aPos
// - vec2 of floats for aTexCoord
// - vec4 of unsigned bytes for aColor
let apos_name = CString::new("aPos").unwrap();
let apos_coord = gl::GetAttribLocation(self.render_program, apos_name.as_ptr());
gl::EnableVertexAttribArray(apos_coord as _);
gl::VertexAttribPointer(
apos_coord as _,
2,
gl::FLOAT,
gl::FALSE,
stride,
bytemuck::offset_of!(piet_hardware::Vertex, pos) as *const _,
);
let atex_name = CString::new("aTexCoord").unwrap();
let atex_coord = gl::GetAttribLocation(self.render_program, atex_name.as_ptr() as _);
gl::EnableVertexAttribArray(atex_coord as _);
gl::VertexAttribPointer(
atex_coord as _,
2,
gl::FLOAT,
gl::FALSE,
stride,
bytemuck::offset_of!(piet_hardware::Vertex, uv) as *const _,
);
let acolor_name = CString::new("aColor").unwrap();
let acolor_coord = gl::GetAttribLocation(self.render_program, acolor_name.as_ptr());
gl::EnableVertexAttribArray(acolor_coord as _);
gl::VertexAttribPointer(
acolor_coord as _,
4,
gl::UNSIGNED_BYTE,
gl::FALSE,
stride,
bytemuck::offset_of!(piet_hardware::Vertex, color) as *const _,
);
// Unbind the vertex array object.
//gl::BindVertexArray(0);
//gl::BindBuffer(gl::ARRAY_BUFFER, 0);
Ok(GlVertexBuffer {
vao,
vbo,
ebo,
num_indices: Cell::new(0),
})
}
}
fn write_vertices(
&mut self,
_device: &(),
_queue: &(),
buffer: &Self::VertexBuffer,
vertices: &[piet_hardware::Vertex],
indices: &[u32],
) {
self.assert_context();
unsafe {
//gl::BindBuffer(gl::ARRAY_BUFFER, buffer.vbo);
//gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, buffer.ebo);
gl::BufferData(
gl::ARRAY_BUFFER,
mem::size_of_val(vertices) as _,
vertices.as_ptr() as *const _,
gl::DYNAMIC_DRAW,
);
gl::BufferData(
gl::ELEMENT_ARRAY_BUFFER,
mem::size_of_val(indices) as _,
indices.as_ptr() as *const _,
gl::DYNAMIC_DRAW,
);
gl_error();
buffer.num_indices.set(indices.len() as _);
}
}
fn push_buffers(
&mut self,
BufferPush {
device: (),
queue: (),
vertex_buffer,
current_texture,
mask_texture,
transform,
viewport_size,
clip,
}: BufferPush<'_, Self>,
) -> Result<(), Self::Error> {
unsafe {
// Use our program.
gl::UseProgram(self.render_program);
// Set the viewport size.
let (width, height) = viewport_size;
gl::Viewport(0, 0, width as i32, height as i32);
gl::Uniform2f(self.viewport_size, width as f32, height as f32);
// Set the scissor rect.
let (sx, sy, s_width, s_height) = match clip {
Some(Rect { x0, y0, x1, y1 }) => (x0, y0, x1 - x0, y1 - y0),
None => (0.0, 0.0, width as f64, height as f64),
};
gl::Enable(gl::SCISSOR_TEST);
gl::Scissor(sx as i32, sy as i32, s_width as i32, s_height as i32);
// Set the transform.
let [a, b, c, d, e, f] = transform.as_coeffs();
let transform = [
a as f32, b as f32, 0.0, c as f32, d as f32, 0.0, e as f32, f as f32, 1.0,
];
gl::UniformMatrix3fv(self.u_transform, 1, gl::FALSE, transform.as_ptr());
// Set the texture.
gl::ActiveTexture(gl::TEXTURE1);
gl::BindTexture(gl::TEXTURE_2D, *current_texture);
gl::Uniform1i(self.tex, 1);
// Set the mask texture.
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_2D, *mask_texture);
gl::Uniform1i(self.mask, 0);
// Set the blend mode.
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
// Set vertex attributes.
gl::BindVertexArray(vertex_buffer.vao);
// Set buffers.
gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.vbo);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_buffer.ebo);
// Draw.
gl::DrawElements(
gl::TRIANGLES,
vertex_buffer.num_indices.get() as i32,
gl::UNSIGNED_INT,
std::ptr::null(),
);
// Unbind everything.
//gl::BindVertexArray(0);
//gl::BindBuffer(gl::ARRAY_BUFFER, 0);
//gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
//gl::BindTexture(gl::TEXTURE_2D, 0);
//gl::UseProgram(0);
}
Ok(())
}
}
fn gl_error() {
let err = unsafe { gl::GetError() };
if err != gl::NO_ERROR {
let error_str = match err {
gl::INVALID_ENUM => "GL_INVALID_ENUM",
gl::INVALID_VALUE => "GL_INVALID_VALUE",
gl::INVALID_OPERATION => "GL_INVALID_OPERATION",
gl::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
gl::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
gl::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
gl::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
gl::CONTEXT_LOST => "GL_CONTEXT_LOST",
_ => "Unknown GL error",
};
log::error!("GL error: {}", error_str)
}
}
const VERTEX_SHADER: &str = "
#version 330 core
in vec2 aPos;
in vec2 aTexCoord;
in vec4 aColor;
out vec4 rgbaColor;
out vec2 fTexCoord;
out vec2 fMaskCoord;
uniform mat3 transform;
uniform vec2 viewportSize;
void main() {
// Transform the vertex position.
vec3 pos = transform * vec3(aPos, 1.0);
pos /= pos.z;
// Transform to screen-space coordinates.
gl_Position = vec4(
(2.0 * pos.x / viewportSize.x) - 1.0,
1.0 - (2.0 * pos.y / viewportSize.y),
0.0,
1.0
);
// Transform to mask-space coordinates.
fMaskCoord = vec2(
pos.x / viewportSize.x,
1.0 - (pos.y / viewportSize.y)
);
rgbaColor = aColor / 255.0;
fTexCoord = aTexCoord;
}
";
const FRAGMENT_SHADER: &str = "
#version 330 core
in vec4 rgbaColor;
in vec2 fTexCoord;
in vec2 fMaskCoord;
uniform sampler2D tex;
uniform sampler2D mask;
void main() {
vec4 textureColor = texture2D(tex, fTexCoord);
vec4 mainColor = rgbaColor * textureColor;
vec4 maskColor = texture2D(mask, fMaskCoord);
vec4 finalColor = mainColor * maskColor;
gl_FragColor = finalColor;
}
";