use std::{mem, rc::Rc};
#[cfg(not(target_arch = "wasm32"))]
use std::ffi::c_void;
#[cfg(all(feature = "glutin", not(target_arch = "wasm32")))]
use glutin::display::GlDisplay;
use fnv::FnvHashMap;
use imgref::ImgVec;
use rgb::RGBA8;
use crate::{
renderer::{GlyphTexture, ImageId, Vertex},
BlendFactor, Color, CompositeOperationState, ErrorKind, FillRule, ImageFilter, ImageInfo, ImageSource, ImageStore,
Scissor,
};
use glow::HasContext;
use super::{Command, CommandType, Params, RenderTarget, Renderer, ShaderType};
mod program;
use program::MainProgram;
mod gl_texture;
use gl_texture::GlTexture;
mod framebuffer;
use framebuffer::Framebuffer;
mod uniform_array;
use uniform_array::UniformArray;
pub struct OpenGl {
debug: bool,
antialias: bool,
is_opengles_2_0: bool,
view: [f32; 2],
screen_view: [f32; 2],
main_programs_with_glyph_texture: [Option<MainProgram>; 7],
main_programs_without_glyph_texture: [Option<MainProgram>; 7],
current_program: u8,
current_program_needs_glyph_texture: bool,
vert_arr: Option<<glow::Context as glow::HasContext>::VertexArray>,
vert_buff: Option<<glow::Context as glow::HasContext>::Buffer>,
framebuffers: FnvHashMap<ImageId, Result<Framebuffer, ErrorKind>>,
context: Rc<glow::Context>,
screen_target: Option<Framebuffer>,
current_render_target: RenderTarget,
}
impl OpenGl {
#[allow(clippy::missing_safety_doc)]
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn new_from_function<F>(load_fn: F) -> Result<Self, ErrorKind>
where
F: FnMut(&str) -> *const c_void,
{
let context = glow::Context::from_loader_function(load_fn);
let version = context.get_parameter_string(glow::VERSION);
let is_opengles_2_0 = version.starts_with("OpenGL ES 2.");
Self::new_from_context(context, is_opengles_2_0)
}
#[allow(clippy::missing_safety_doc)]
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn new_from_function_cstr<F>(load_fn: F) -> Result<Self, ErrorKind>
where
F: FnMut(&std::ffi::CStr) -> *const c_void,
{
let context = glow::Context::from_loader_function_cstr(load_fn);
let version = context.get_parameter_string(glow::VERSION);
let is_opengles_2_0 = version.starts_with("OpenGL ES 2.");
Self::new_from_context(context, is_opengles_2_0)
}
#[cfg(all(feature = "glutin", not(target_arch = "wasm32")))]
pub fn new_from_glutin_display(display: &impl GlDisplay) -> Result<Self, ErrorKind> {
unsafe { OpenGl::new_from_function_cstr(|s| display.get_proc_address(s) as *const _) }
}
#[cfg(target_arch = "wasm32")]
pub fn new_from_html_canvas(canvas: &web_sys::HtmlCanvasElement) -> Result<Self, ErrorKind> {
let mut attrs = web_sys::WebGlContextAttributes::new();
attrs.stencil(true);
attrs.antialias(false);
use wasm_bindgen::JsCast;
let webgl1_context = match canvas.get_context_with_context_options("webgl", &attrs) {
Ok(Some(context)) => context.dyn_into::<web_sys::WebGlRenderingContext>().unwrap(),
_ => {
return Err(ErrorKind::GeneralError(
"Canvas::getContext failed to retrieve WebGL 1 context".to_owned(),
))
}
};
let context = glow::Context::from_webgl1_context(webgl1_context);
Self::new_from_context(context, true)
}
fn new_from_context(context: glow::Context, is_opengles_2_0: bool) -> Result<Self, ErrorKind> {
let debug = cfg!(debug_assertions);
let antialias = true;
let context = Rc::new(context);
let generate_shader_program_variants = |with_glyph_texture| -> Result<_, ErrorKind> {
Ok([
Some(MainProgram::new(
&context,
antialias,
ShaderType::FillGradient,
with_glyph_texture,
)?),
Some(MainProgram::new(
&context,
antialias,
ShaderType::FillImage,
with_glyph_texture,
)?),
if with_glyph_texture {
None
} else {
Some(MainProgram::new(&context, antialias, ShaderType::Stencil, false)?)
},
Some(MainProgram::new(
&context,
antialias,
ShaderType::FillImageGradient,
with_glyph_texture,
)?),
if with_glyph_texture {
None
} else {
Some(MainProgram::new(&context, antialias, ShaderType::FilterImage, false)?)
},
Some(MainProgram::new(
&context,
antialias,
ShaderType::FillColor,
with_glyph_texture,
)?),
if with_glyph_texture {
None
} else {
Some(MainProgram::new(
&context,
antialias,
ShaderType::TextureCopyUnclipped,
false,
)?)
},
])
};
let main_programs_with_glyph_texture = generate_shader_program_variants(true)?;
let main_programs_without_glyph_texture = generate_shader_program_variants(false)?;
let mut opengl = OpenGl {
debug,
antialias,
is_opengles_2_0: false,
view: [0.0, 0.0],
screen_view: [0.0, 0.0],
main_programs_with_glyph_texture,
main_programs_without_glyph_texture,
current_program: 0,
current_program_needs_glyph_texture: true,
vert_arr: Default::default(),
vert_buff: Default::default(),
framebuffers: Default::default(),
context,
screen_target: None,
current_render_target: RenderTarget::Screen,
};
unsafe {
opengl.is_opengles_2_0 = is_opengles_2_0;
opengl.vert_arr = opengl.context.create_vertex_array().ok();
opengl.vert_buff = opengl.context.create_buffer().ok();
}
Ok(opengl)
}
pub fn is_opengles(&self) -> bool {
self.is_opengles_2_0
}
fn check_error(&self, label: &str) {
if !self.debug {
return;
}
let err = unsafe { self.context.get_error() };
if err == glow::NO_ERROR {
return;
}
let message = match err {
glow::INVALID_ENUM => "Invalid enum",
glow::INVALID_VALUE => "Invalid value",
glow::INVALID_OPERATION => "Invalid operation",
glow::OUT_OF_MEMORY => "Out of memory",
glow::INVALID_FRAMEBUFFER_OPERATION => "Invalid framebuffer operation",
_ => "Unknown error",
};
eprintln!("({}) Error on {} - {}", err, label, message);
}
fn gl_factor(factor: BlendFactor) -> u32 {
match factor {
BlendFactor::Zero => glow::ZERO,
BlendFactor::One => glow::ONE,
BlendFactor::SrcColor => glow::SRC_COLOR,
BlendFactor::OneMinusSrcColor => glow::ONE_MINUS_SRC_COLOR,
BlendFactor::DstColor => glow::DST_COLOR,
BlendFactor::OneMinusDstColor => glow::ONE_MINUS_DST_COLOR,
BlendFactor::SrcAlpha => glow::SRC_ALPHA,
BlendFactor::OneMinusSrcAlpha => glow::ONE_MINUS_SRC_ALPHA,
BlendFactor::DstAlpha => glow::DST_ALPHA,
BlendFactor::OneMinusDstAlpha => glow::ONE_MINUS_DST_ALPHA,
BlendFactor::SrcAlphaSaturate => glow::SRC_ALPHA_SATURATE,
}
}
fn set_composite_operation(&self, blend_state: CompositeOperationState) {
unsafe {
self.context.blend_func_separate(
Self::gl_factor(blend_state.src_rgb),
Self::gl_factor(blend_state.dst_rgb),
Self::gl_factor(blend_state.src_alpha),
Self::gl_factor(blend_state.dst_alpha),
);
}
}
fn convex_fill(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, gpu_paint: &Params) {
self.set_uniforms(images, gpu_paint, cmd.image, cmd.glyph_texture);
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.fill_verts {
unsafe {
self.context.draw_arrays(glow::TRIANGLE_FAN, start as i32, count as i32);
}
}
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
self.check_error("convex_fill");
}
fn concave_fill(
&mut self,
images: &ImageStore<GlTexture>,
cmd: &Command,
stencil_paint: &Params,
fill_paint: &Params,
) {
unsafe {
self.context.enable(glow::STENCIL_TEST);
self.context.stencil_mask(0xff);
self.context.stencil_func(glow::ALWAYS, 0, 0xff);
self.context.color_mask(false, false, false, false);
}
self.set_uniforms(images, stencil_paint, None, GlyphTexture::None);
unsafe {
self.context
.stencil_op_separate(glow::FRONT, glow::KEEP, glow::KEEP, glow::INCR_WRAP);
self.context
.stencil_op_separate(glow::BACK, glow::KEEP, glow::KEEP, glow::DECR_WRAP);
self.context.disable(glow::CULL_FACE);
}
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.fill_verts {
unsafe {
self.context.draw_arrays(glow::TRIANGLE_FAN, start as i32, count as i32);
}
}
}
unsafe {
self.context.enable(glow::CULL_FACE);
self.context.color_mask(true, true, true, true);
}
self.set_uniforms(images, fill_paint, cmd.image, cmd.glyph_texture);
if self.antialias {
unsafe {
match cmd.fill_rule {
FillRule::NonZero => self.context.stencil_func(glow::EQUAL, 0x0, 0xff),
FillRule::EvenOdd => self.context.stencil_func(glow::EQUAL, 0x0, 0x1),
}
self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
}
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
}
unsafe {
match cmd.fill_rule {
FillRule::NonZero => self.context.stencil_func(glow::NOTEQUAL, 0x0, 0xff),
FillRule::EvenOdd => self.context.stencil_func(glow::NOTEQUAL, 0x0, 0x1),
}
self.context.stencil_op(glow::ZERO, glow::ZERO, glow::ZERO);
if let Some((start, count)) = cmd.triangles_verts {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
self.context.disable(glow::STENCIL_TEST);
}
self.check_error("concave_fill");
}
fn stroke(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint: &Params) {
self.set_uniforms(images, paint, cmd.image, cmd.glyph_texture);
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
self.check_error("stroke");
}
fn stencil_stroke(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint1: &Params, paint2: &Params) {
unsafe {
self.context.enable(glow::STENCIL_TEST);
self.context.stencil_mask(0xff);
self.context.stencil_func(glow::EQUAL, 0x0, 0xff);
self.context.stencil_op(glow::KEEP, glow::KEEP, glow::INCR);
}
self.set_uniforms(images, paint2, cmd.image, cmd.glyph_texture);
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
self.set_uniforms(images, paint1, cmd.image, cmd.glyph_texture);
unsafe {
self.context.stencil_func(glow::EQUAL, 0x0, 0xff);
self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
}
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
unsafe {
self.context.color_mask(false, false, false, false);
self.context.stencil_func(glow::ALWAYS, 0x0, 0xff);
self.context.stencil_op(glow::ZERO, glow::ZERO, glow::ZERO);
}
for drawable in &cmd.drawables {
if let Some((start, count)) = drawable.stroke_verts {
unsafe {
self.context
.draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32);
}
}
}
unsafe {
self.context.color_mask(true, true, true, true);
self.context.disable(glow::STENCIL_TEST);
}
self.check_error("stencil_stroke");
}
fn triangles(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint: &Params) {
self.set_uniforms(images, paint, cmd.image, cmd.glyph_texture);
if let Some((start, count)) = cmd.triangles_verts {
unsafe {
self.context.draw_arrays(glow::TRIANGLES, start as i32, count as i32);
}
}
self.check_error("triangles");
}
fn set_uniforms(
&mut self,
images: &ImageStore<GlTexture>,
paint: &Params,
image_tex: Option<ImageId>,
glyph_tex: GlyphTexture,
) {
self.select_main_program(paint);
let arr = UniformArray::from(paint);
self.main_program().set_config(arr.as_slice());
self.check_error("set_uniforms uniforms");
let tex = image_tex.and_then(|id| images.get(id)).map(|tex| tex.id());
unsafe {
self.context.active_texture(glow::TEXTURE0);
self.context.bind_texture(glow::TEXTURE_2D, tex);
}
let glyphtex = match glyph_tex {
GlyphTexture::None => None,
GlyphTexture::AlphaMask(id) | GlyphTexture::ColorTexture(id) => images.get(id).map(|tex| tex.id()),
};
unsafe {
self.context.active_texture(glow::TEXTURE0 + 1);
self.context.bind_texture(glow::TEXTURE_2D, glyphtex);
}
self.check_error("set_uniforms texture");
}
fn clear_rect(&self, x: u32, y: u32, width: u32, height: u32, color: Color) {
unsafe {
self.context.enable(glow::SCISSOR_TEST);
self.context.scissor(
x as i32,
self.view[1] as i32 - (height as i32 + y as i32),
width as i32,
height as i32,
);
self.context.clear_color(color.r, color.g, color.b, color.a);
self.context.clear(glow::COLOR_BUFFER_BIT | glow::STENCIL_BUFFER_BIT);
self.context.disable(glow::SCISSOR_TEST);
}
}
fn set_target(&mut self, images: &ImageStore<GlTexture>, target: RenderTarget) {
self.current_render_target = target;
match (target, &self.screen_target) {
(RenderTarget::Screen, None) => unsafe {
Framebuffer::unbind(&self.context);
self.view = self.screen_view;
self.context.viewport(0, 0, self.view[0] as i32, self.view[1] as i32);
},
(RenderTarget::Screen, Some(framebuffer)) => {
framebuffer.bind();
self.view = self.screen_view;
unsafe {
self.context.viewport(0, 0, self.view[0] as i32, self.view[1] as i32);
}
}
(RenderTarget::Image(id), _) => {
let context = self.context.clone();
if let Some(texture) = images.get(id) {
if let Ok(fb) = self
.framebuffers
.entry(id)
.or_insert_with(|| Framebuffer::new(&context, texture))
{
fb.bind();
self.view[0] = texture.info().width() as f32;
self.view[1] = texture.info().height() as f32;
unsafe {
self.context
.viewport(0, 0, texture.info().width() as i32, texture.info().height() as i32);
}
}
}
}
}
}
pub fn set_screen_target(&mut self, framebuffer_object: Option<<glow::Context as glow::HasContext>::Framebuffer>) {
match framebuffer_object {
Some(fbo_id) => self.screen_target = Some(Framebuffer::from_external(&self.context, fbo_id)),
None => self.screen_target = None,
}
}
fn render_filtered_image(
&mut self,
images: &mut ImageStore<GlTexture>,
cmd: Command,
target_image: ImageId,
filter: ImageFilter,
) {
match filter {
ImageFilter::GaussianBlur { sigma } => self.render_gaussian_blur(images, cmd, target_image, sigma),
}
}
fn render_gaussian_blur(
&mut self,
images: &mut ImageStore<GlTexture>,
mut cmd: Command,
target_image: ImageId,
sigma: f32,
) {
let original_render_target = self.current_render_target;
let source_image_info = images.get(cmd.image.unwrap()).unwrap().info();
let image_paint = crate::Paint::image(
cmd.image.unwrap(),
0.,
0.,
source_image_info.width() as _,
source_image_info.height() as _,
0.,
1.,
);
let mut blur_params = Params::new(
images,
&Default::default(),
&image_paint.flavor,
&Default::default(),
&Scissor::default(),
0.,
0.,
0.,
);
blur_params.shader_type = ShaderType::FilterImage;
let gauss_coeff_x = 1. / ((2. * std::f32::consts::PI).sqrt() * sigma);
let gauss_coeff_y = f32::exp(-0.5 / (sigma * sigma));
let gauss_coeff_z = gauss_coeff_y * gauss_coeff_y;
blur_params.image_blur_filter_coeff[0] = gauss_coeff_x;
blur_params.image_blur_filter_coeff[1] = gauss_coeff_y;
blur_params.image_blur_filter_coeff[2] = gauss_coeff_z;
blur_params.image_blur_filter_direction = [1.0, 0.0];
blur_params.image_blur_filter_sigma = sigma.min(8.);
let horizontal_blur_buffer = images.alloc(self, source_image_info).unwrap();
self.set_target(images, RenderTarget::Image(horizontal_blur_buffer));
self.main_program().set_view(self.view);
self.clear_rect(
0,
0,
source_image_info.width() as _,
source_image_info.height() as _,
Color::rgbaf(0., 0., 0., 0.),
);
self.triangles(images, &cmd, &blur_params);
self.set_target(images, RenderTarget::Image(target_image));
self.main_program().set_view(self.view);
self.clear_rect(
0,
0,
source_image_info.width() as _,
source_image_info.height() as _,
Color::rgbaf(0., 0., 0., 0.),
);
blur_params.image_blur_filter_direction = [0.0, 1.0];
cmd.image = Some(horizontal_blur_buffer);
self.triangles(images, &cmd, &blur_params);
images.remove(self, horizontal_blur_buffer);
self.set_target(images, original_render_target);
self.main_program().set_view(self.view);
}
fn main_program(&self) -> &MainProgram {
let programs = if self.current_program_needs_glyph_texture {
&self.main_programs_with_glyph_texture
} else {
&self.main_programs_without_glyph_texture
};
programs[self.current_program as usize]
.as_ref()
.expect("internal error: invalid shader program selected for given paint")
}
fn select_main_program(&mut self, params: &Params) {
let program_index = params.shader_type.to_u8();
if program_index != self.current_program
|| params.uses_glyph_texture() != self.current_program_needs_glyph_texture
{
unsafe {
self.context.active_texture(glow::TEXTURE0);
self.context.bind_texture(glow::TEXTURE_2D, None);
self.context.active_texture(glow::TEXTURE0 + 1);
self.context.bind_texture(glow::TEXTURE_2D, None);
}
self.main_program().unbind();
self.current_program = program_index;
self.current_program_needs_glyph_texture = params.uses_glyph_texture();
let program = self.main_program();
program.bind();
program.set_tex(0);
program.set_glyphtex(1);
program.set_view(self.view);
}
}
}
impl Renderer for OpenGl {
type Image = GlTexture;
type NativeTexture = <glow::Context as glow::HasContext>::Texture;
fn set_size(&mut self, width: u32, height: u32, _dpi: f32) {
self.view[0] = width as f32;
self.view[1] = height as f32;
self.screen_view = self.view;
unsafe {
self.context.viewport(0, 0, width as i32, height as i32);
}
}
fn render(&mut self, images: &mut ImageStore<Self::Image>, verts: &[Vertex], commands: Vec<Command>) {
self.current_program = 0;
self.main_program().bind();
unsafe {
self.context.enable(glow::CULL_FACE);
self.context.cull_face(glow::BACK);
self.context.front_face(glow::CCW);
self.context.enable(glow::BLEND);
self.context.disable(glow::DEPTH_TEST);
self.context.disable(glow::SCISSOR_TEST);
self.context.color_mask(true, true, true, true);
self.context.stencil_mask(0xffff_ffff);
self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
self.context.stencil_func(glow::ALWAYS, 0, 0xffff_ffff);
self.context.active_texture(glow::TEXTURE0);
self.context.bind_texture(glow::TEXTURE_2D, None);
self.context.active_texture(glow::TEXTURE0 + 1);
self.context.bind_texture(glow::TEXTURE_2D, None);
self.context.bind_vertex_array(self.vert_arr);
let vertex_size = mem::size_of::<Vertex>();
self.context.bind_buffer(glow::ARRAY_BUFFER, self.vert_buff);
self.context
.buffer_data_u8_slice(glow::ARRAY_BUFFER, verts.align_to().1, glow::STREAM_DRAW);
self.context.enable_vertex_attrib_array(0);
self.context.enable_vertex_attrib_array(1);
self.context
.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, vertex_size as i32, 0);
self.context.vertex_attrib_pointer_f32(
1,
2,
glow::FLOAT,
false,
vertex_size as i32,
2 * mem::size_of::<f32>() as i32,
);
}
self.check_error("render prepare");
for cmd in commands.into_iter() {
self.set_composite_operation(cmd.composite_operation);
match cmd.cmd_type {
CommandType::ConvexFill { ref params } => self.convex_fill(images, &cmd, params),
CommandType::ConcaveFill {
ref stencil_params,
ref fill_params,
} => self.concave_fill(images, &cmd, stencil_params, fill_params),
CommandType::Stroke { ref params } => self.stroke(images, &cmd, params),
CommandType::StencilStroke {
ref params1,
ref params2,
} => self.stencil_stroke(images, &cmd, params1, params2),
CommandType::Triangles { ref params } => self.triangles(images, &cmd, params),
CommandType::ClearRect {
x,
y,
width,
height,
color,
} => {
self.clear_rect(x, y, width, height, color);
}
CommandType::SetRenderTarget(target) => {
self.set_target(images, target);
self.main_program().set_view(self.view);
}
CommandType::RenderFilteredImage { target_image, filter } => {
self.render_filtered_image(images, cmd, target_image, filter)
}
}
}
unsafe {
self.context.disable_vertex_attrib_array(0);
self.context.disable_vertex_attrib_array(1);
self.context.bind_vertex_array(None);
self.context.disable(glow::CULL_FACE);
self.context.bind_buffer(glow::ARRAY_BUFFER, None);
self.context.bind_texture(glow::TEXTURE_2D, None);
}
self.main_program().unbind();
self.check_error("render done");
}
fn alloc_image(&mut self, info: ImageInfo) -> Result<Self::Image, ErrorKind> {
Self::Image::new(&self.context, info, self.is_opengles_2_0)
}
fn create_image_from_native_texture(
&mut self,
native_texture: Self::NativeTexture,
info: ImageInfo,
) -> Result<Self::Image, ErrorKind> {
Ok(Self::Image::new_from_native_texture(native_texture, info))
}
fn update_image(
&mut self,
image: &mut Self::Image,
data: ImageSource,
x: usize,
y: usize,
) -> Result<(), ErrorKind> {
image.update(&self.context, data, x, y, self.is_opengles_2_0)
}
fn delete_image(&mut self, image: Self::Image, image_id: ImageId) {
self.framebuffers.remove(&image_id);
image.delete(&self.context);
}
fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> {
let w = self.view[0] as usize;
let h = self.view[1] as usize;
let mut image = ImgVec::new(
vec![
RGBA8 {
r: 255,
g: 255,
b: 255,
a: 255
};
w * h
],
w,
h,
);
unsafe {
self.context.read_pixels(
0,
0,
self.view[0] as i32,
self.view[1] as i32,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelPackData::Slice(image.buf_mut().align_to_mut().1),
);
}
let mut flipped = Vec::with_capacity(w * h);
for row in image.rows().rev() {
flipped.extend_from_slice(row);
}
Ok(ImgVec::new(flipped, w, h))
}
}
impl Drop for OpenGl {
fn drop(&mut self) {
if let Some(vert_arr) = self.vert_arr {
unsafe {
self.context.delete_vertex_array(vert_arr);
}
}
if let Some(vert_buff) = self.vert_buff {
unsafe {
self.context.delete_buffer(vert_buff);
}
}
}
}